@depthhq/maker-sdk 0.1.16 → 0.1.18

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.
@@ -0,0 +1,2 @@
1
+ import { Connection } from "@solana/web3.js";
2
+ export declare function chainTime(connection: Pick<Connection, "getAccountInfo">): Promise<number>;
@@ -5,4 +5,7 @@ export type EthereumProvider = {
5
5
  params?: unknown[];
6
6
  }): Promise<unknown>;
7
7
  };
8
- export declare function connect_hyperliquid(provider: EthereumProvider): Promise<AbstractViemJsonRpcAccount>;
8
+ export declare function connect_hyperliquid(provider: EthereumProvider, options?: {
9
+ timeoutMs?: number;
10
+ signal?: AbortSignal;
11
+ }): Promise<AbstractViemJsonRpcAccount>;
package/dist/index.js CHANGED
@@ -1313,6 +1313,19 @@ var resolveObjectURL = notimpl("resolveObjectURL");
1313
1313
  var isUtf8 = notimpl("isUtf8");
1314
1314
  var transcode = notimpl("transcode");
1315
1315
 
1316
+ // src/chain/clock.ts
1317
+ import { PublicKey, SYSVAR_CLOCK_PUBKEY } from "@solana/web3.js";
1318
+ var owner = new PublicKey("Sysvar1111111111111111111111111111111111111");
1319
+ async function chainTime(connection) {
1320
+ const clock = await connection.getAccountInfo(SYSVAR_CLOCK_PUBKEY, "confirmed");
1321
+ if (!clock?.owner.equals(owner) || clock.data.length !== 40)
1322
+ throw new Error("Solana clock sysvar is unavailable");
1323
+ const time = clock.data.readBigInt64LE(32);
1324
+ if (time <= 0n || time > BigInt(Number.MAX_SAFE_INTEGER))
1325
+ throw new Error("Solana clock timestamp is invalid");
1326
+ return Number(time);
1327
+ }
1328
+
1316
1329
  // src/client/client.ts
1317
1330
  import * as anchor6 from "@anchor-lang/core";
1318
1331
 
@@ -3722,7 +3735,7 @@ var { default: fs} = (() => ({}));
3722
3735
  import * as anchor from "@anchor-lang/core";
3723
3736
  import * as anchor__default from "@anchor-lang/core";
3724
3737
  import { BorshCoder, convertIdlToCamelCase, Program } from "@anchor-lang/core";
3725
- import { AddressLookupTableProgram, PublicKey } from "@solana/web3.js";
3738
+ import { AddressLookupTableProgram, PublicKey as PublicKey2 } from "@solana/web3.js";
3726
3739
  var CURVE25519_SCALAR_FIELD_MODULUS = ed25519.CURVE.n;
3727
3740
  function serializeLE(val, lengthInBytes) {
3728
3741
  const result = new Uint8Array(lengthInBytes);
@@ -26329,7 +26342,7 @@ function findArciumPda(seeds) {
26329
26342
  return findPda(getArciumProgramId(), seeds);
26330
26343
  }
26331
26344
  function findPda(programId, seeds) {
26332
- return PublicKey.findProgramAddressSync(seeds, programId)[0];
26345
+ return PublicKey2.findProgramAddressSync(seeds, programId)[0];
26333
26346
  }
26334
26347
  function assertIntegerInRange(value, min, max, label) {
26335
26348
  if (!Number.isInteger(value) || value < min || value > max) {
@@ -26401,7 +26414,7 @@ import {
26401
26414
  } from "@depthhq/bindings";
26402
26415
  import { DepthView } from "@depthhq/sdk/views";
26403
26416
  import { DepthRecovery } from "@depthhq/sdk/recovery";
26404
- import { PublicKey as PublicKey7 } from "@solana/web3.js";
26417
+ import { PublicKey as PublicKey8 } from "@solana/web3.js";
26405
26418
 
26406
26419
  // src/chain/random.ts
26407
26420
  function randomBytes3(length) {
@@ -26421,8 +26434,8 @@ function order(value, receipt = false) {
26421
26434
  return true;
26422
26435
  return ["filled", "cancelled", "rejected"].includes(String(value.status)) && integer(value.completedAt) && value.completedAt >= value.nonce && amount(value.filledSize) && BigInt(value.filledSize) >= 0n && BigInt(value.filledSize) <= BigInt(value.size) && (value.filledAt === undefined || integer(value.filledAt) && value.filledAt >= value.nonce - 1000 && value.filledAt <= value.completedAt + 1000) && optional(value.orderId, integer) && optional(value.averagePriceE8, amount) && optional(value.fee, amount) && optional(value.reason, text) && optional(value.hashes, (hashes) => Array.isArray(hashes) && hashes.length <= 2000 && hashes.every((hash) => typeof hash === "string" && /^0x[0-9a-fA-F]{64}$/.test(hash)));
26423
26436
  }
26424
- function hedgeView(value, owner) {
26425
- if (!object(value) || value.maker !== owner || typeof value.enabled !== "boolean")
26437
+ function hedgeView(value, owner2) {
26438
+ if (!object(value) || value.maker !== owner2 || typeof value.enabled !== "boolean")
26426
26439
  throw new Error("Maker hedge response belongs to another wallet or is invalid");
26427
26440
  const snapshot = value.snapshot;
26428
26441
  if (snapshot !== null && (!value.enabled || !object(snapshot) || snapshot.venue !== "hyperliquid" || !["testnet", "mainnet"].includes(String(snapshot.network)) || typeof snapshot.account !== "string" || !/^0x[0-9a-fA-F]{40}$/.test(snapshot.account) || !integer(snapshot.updatedAt) || !["balanced", "waiting", "pending", "blocked"].includes(String(snapshot.phase)) || !optional(snapshot.reason, text) || !optional(snapshot.depthPosition, amount) || !optional(snapshot.externalPosition, amount) || !optional(snapshot.unhedgedPosition, amount) || !optional(snapshot.pending, order) || !Array.isArray(snapshot.history) || snapshot.history.length > 50 || !snapshot.history.every((record) => order(record, true))))
@@ -26513,7 +26526,7 @@ class MakerAccess extends WalletSession {
26513
26526
  }
26514
26527
 
26515
26528
  // src/automation/automation.ts
26516
- import { PublicKey as PublicKey3, SystemProgram, Transaction } from "@solana/web3.js";
26529
+ import { PublicKey as PublicKey4, SystemProgram, Transaction } from "@solana/web3.js";
26517
26530
 
26518
26531
  // src/hedge/setup.ts
26519
26532
  import { ExchangeClient, HttpTransport, InfoClient } from "@nktkas/hyperliquid";
@@ -26600,7 +26613,7 @@ async function setup_hyperliquid(options) {
26600
26613
  }
26601
26614
 
26602
26615
  // src/automation/types.ts
26603
- import { PublicKey as PublicKey2 } from "@solana/web3.js";
26616
+ import { PublicKey as PublicKey3 } from "@solana/web3.js";
26604
26617
 
26605
26618
  // src/bot/policy.ts
26606
26619
  import { positionPnl } from "@depthhq/sdk";
@@ -26733,7 +26746,7 @@ function automationConfig(value) {
26733
26746
  };
26734
26747
  }
26735
26748
  var automationWire = (config) => JSON.parse(JSON.stringify(config, (_key, value) => typeof value === "bigint" ? value.toString() : value));
26736
- function automationView(value, owner, environment) {
26749
+ function automationView(value, owner2, environment) {
26737
26750
  if (value === null)
26738
26751
  return null;
26739
26752
  const view = object2(value, [
@@ -26751,9 +26764,9 @@ function automationView(value, owner, environment) {
26751
26764
  "hedge",
26752
26765
  "funding"
26753
26766
  ]);
26754
- if (view.owner !== owner || view.environment !== environment)
26767
+ if (view.owner !== owner2 || view.environment !== environment)
26755
26768
  throw new Error("Automation belongs to another wallet or network");
26756
- if (typeof view.operator !== "string" || new PublicKey2(view.operator).toBase58() !== view.operator || typeof view.agent !== "string" || !/^0x[0-9a-fA-F]{40}$/.test(view.agent) || integer2(view.revision) < 1 || integer2(view.updatedAt) < 1 || !["running", "paused"].includes(String(view.desired)) || ![
26769
+ if (typeof view.operator !== "string" || new PublicKey3(view.operator).toBase58() !== view.operator || typeof view.agent !== "string" || !/^0x[0-9a-fA-F]{40}$/.test(view.agent) || integer2(view.revision) < 1 || integer2(view.updatedAt) < 1 || !["running", "paused"].includes(String(view.desired)) || ![
26757
26770
  "configured",
26758
26771
  "starting",
26759
26772
  "running",
@@ -26765,10 +26778,10 @@ function automationView(value, owner, environment) {
26765
26778
  automationConfig(view.config);
26766
26779
  if (view.bot !== undefined) {
26767
26780
  const bot = object2(view.bot, ["phase", "reason", "quoteId", "fillCount"]);
26768
- if (!["active", "pending", "idle", "stopped"].includes(String(bot.phase)) || integer2(bot.fillCount) < 0 || bot.reason !== undefined && typeof bot.reason !== "string" || bot.quoteId !== undefined && (typeof bot.quoteId !== "string" || new PublicKey2(bot.quoteId).toBase58() !== bot.quoteId))
26781
+ if (!["active", "pending", "idle", "stopped"].includes(String(bot.phase)) || integer2(bot.fillCount) < 0 || bot.reason !== undefined && typeof bot.reason !== "string" || bot.quoteId !== undefined && (typeof bot.quoteId !== "string" || new PublicKey3(bot.quoteId).toBase58() !== bot.quoteId))
26769
26782
  throw new Error("Automation status is invalid");
26770
26783
  }
26771
- hedgeView(view.hedge, owner);
26784
+ hedgeView(view.hedge, owner2);
26772
26785
  if (view.funding !== undefined) {
26773
26786
  const funding = object2(view.funding, ["signature", "lamports", "status"]);
26774
26787
  if (typeof funding.signature !== "string" || !/^[1-9A-HJ-NP-Za-km-z]{64,90}$/.test(funding.signature) || !["pending", "confirmed", "expired", "failed"].includes(String(funding.status)))
@@ -26840,7 +26853,7 @@ class MakerAutomation extends WalletSession {
26840
26853
  maxLeverage: config.hedge.policy.maxLeverage
26841
26854
  });
26842
26855
  }
26843
- const operator = new PublicKey3(profile.operator);
26856
+ const operator = new PublicKey4(profile.operator);
26844
26857
  const balance = BigInt(await this.maker.options.connection.getBalance(operator, "confirmed"));
26845
26858
  const signatures = [];
26846
26859
  const recovered = this.view(await this.authorizedRequest("fund", { revision: current.revision }));
@@ -26952,7 +26965,7 @@ async function queueAccounts(provider, connection, name, offset) {
26952
26965
  import * as anchor2 from "@anchor-lang/core";
26953
26966
  import { DEPTH_PROGRAM_ID as DEPTH_PROGRAM_ID2, SOL_USD_MARKET_ID } from "@depthhq/bindings";
26954
26967
  import { DepthError } from "@depthhq/sdk";
26955
- import { PublicKey as PublicKey4 } from "@solana/web3.js";
26968
+ import { PublicKey as PublicKey5 } from "@solana/web3.js";
26956
26969
  function u64(value, name) {
26957
26970
  if (typeof value !== "bigint" || value < 0n || value > (1n << 64n) - 1n)
26958
26971
  throw new RangeError(`${name} must be an unsigned 64-bit bigint`);
@@ -26969,7 +26982,7 @@ function timing(options) {
26969
26982
  }
26970
26983
  function address2(market, maker, nonce) {
26971
26984
  const bytes = u64(nonce, "clientNonce").toArrayLike(Buffer, "le", 8);
26972
- return PublicKey4.findProgramAddressSync([Buffer.from("backstop"), market.toBuffer(), maker.toBuffer(), bytes], DEPTH_PROGRAM_ID2)[0];
26985
+ return PublicKey5.findProgramAddressSync([Buffer.from("backstop"), market.toBuffer(), maker.toBuffer(), bytes], DEPTH_PROGRAM_ID2)[0];
26973
26986
  }
26974
26987
  async function poll(read2, terminal, options) {
26975
26988
  const { timeoutMs, pollMs } = timing(options);
@@ -27011,7 +27024,7 @@ class BackstopHandle {
27011
27024
  ]);
27012
27025
  const now = Math.floor(Date.now() / 1000);
27013
27026
  const margin = (input.maxCapacity * BigInt(market.config.initialMarginBps) + 9999n) / 10000n;
27014
- if (!market.backstopOffer.equals(PublicKey4.default) || market.pendingOrders !== 0 || Object.keys(market.status)[0] === "settling" || input.maxCapacity > BigInt(market.config.maxAccountNotional.toString()) || margin < BigInt(market.config.backstopMinCollateral.toString()))
27027
+ if (!market.backstopOffer.equals(PublicKey5.default) || market.pendingOrders !== 0 || Object.keys(market.status)[0] === "settling" || input.maxCapacity > BigInt(market.config.maxAccountNotional.toString()) || margin < BigInt(market.config.backstopMinCollateral.toString()))
27015
27028
  throw new Error("Backstop offer is outside the current market policy");
27016
27029
  if (!account.owner.equals(client.options.maker) || !account.market.equals(client.marketAddress) || !("ready" in account.status))
27017
27030
  throw new Error("Maker market account is unavailable");
@@ -27113,7 +27126,7 @@ class BackstopRelease {
27113
27126
 
27114
27127
  // src/fill/history.ts
27115
27128
  import * as anchor3 from "@anchor-lang/core";
27116
- import { PublicKey as PublicKey5 } from "@solana/web3.js";
27129
+ import { PublicKey as PublicKey6 } from "@solana/web3.js";
27117
27130
 
27118
27131
  class FillHistory {
27119
27132
  client;
@@ -27176,7 +27189,7 @@ class FillHistory {
27176
27189
  const fills = [];
27177
27190
  for (let i2 = 0;i2 < addresses.length; i2 += 100) {
27178
27191
  const batch = addresses.slice(i2, i2 + 100);
27179
- const accounts2 = await connection.getMultipleAccountsInfo(batch.map((id) => new PublicKey5(id)), "finalized");
27192
+ const accounts2 = await connection.getMultipleAccountsInfo(batch.map((id) => new PublicKey6(id)), "finalized");
27180
27193
  accounts2.forEach((info, index) => {
27181
27194
  if (!info || !info.owner.equals(this.client.program.programId))
27182
27195
  throw new Error("Finalized fill receipt is unavailable");
@@ -27321,7 +27334,7 @@ class QuoteHandle {
27321
27334
  const market = await this.client.read("Market", this.client.marketAddress);
27322
27335
  if (!Number.isSafeInteger(input.validForSeconds) || input.validForSeconds < 1 || input.validForSeconds > market.config.maxQuoteLifetimeSeconds)
27323
27336
  throw new RangeError("Quote duration is outside market policy");
27324
- const expiry = Math.floor(Date.now() / 1000) + input.validForSeconds;
27337
+ const expiry = await chainTime(this.client.options.connection) + input.validForSeconds;
27325
27338
  const before = await this.status();
27326
27339
  if (before.status !== "active")
27327
27340
  throw new Error("Only active quotes can be renewed");
@@ -27587,7 +27600,7 @@ class QuoteHandle {
27587
27600
  // src/quote/replacement.ts
27588
27601
  import * as anchor5 from "@anchor-lang/core";
27589
27602
  import { DEPTH_PROGRAM_ID as DEPTH_PROGRAM_ID3 } from "@depthhq/bindings";
27590
- import { PublicKey as PublicKey6 } from "@solana/web3.js";
27603
+ import { PublicKey as PublicKey7 } from "@solana/web3.js";
27591
27604
  import { quoteBounds as quoteBounds3 } from "@depthhq/bindings";
27592
27605
 
27593
27606
  class QuoteReplacement {
@@ -27603,7 +27616,7 @@ class QuoteReplacement {
27603
27616
  throw new RangeError("clientNonce must be an unsigned 64-bit bigint");
27604
27617
  }
27605
27618
  const bytes = new anchor5.BN(nonce.toString()).toArrayLike(Buffer, "le", 8);
27606
- this.nextId = PublicKey6.findProgramAddressSync([
27619
+ this.nextId = PublicKey7.findProgramAddressSync([
27607
27620
  Buffer.from("quote"),
27608
27621
  client.marketAddress.toBuffer(),
27609
27622
  client.options.maker.toBuffer(),
@@ -27786,7 +27799,7 @@ async function cleanupQuotes(client, options = {}) {
27786
27799
  throw new RangeError("Cleanup limit must be between 1 and 12");
27787
27800
  const excluded = new Set(options.exclude);
27788
27801
  const actor = client.options.wallet.publicKey;
27789
- const owner = actor.equals(client.options.maker);
27802
+ const owner2 = actor.equals(client.options.maker);
27790
27803
  const transaction = new Transaction2().add(ComputeBudgetProgram.setComputeUnitLimit({ units: 400000 }));
27791
27804
  let closed = 0;
27792
27805
  let reclaimedLamports = 0n;
@@ -27797,7 +27810,7 @@ async function cleanupQuotes(client, options = {}) {
27797
27810
  if (excluded.has(item.id.toBase58()))
27798
27811
  continue;
27799
27812
  const state = await client.quoteState(item.id);
27800
- if (!["cancelled", "filled", "rejected"].includes(Object.keys(state.status)[0]) || state.attachedOrders !== 0 || !owner && !state.operator.equals(actor))
27813
+ if (!["cancelled", "filled", "rejected"].includes(Object.keys(state.status)[0]) || state.attachedOrders !== 0 || !owner2 && !state.operator.equals(actor))
27801
27814
  continue;
27802
27815
  if (state.replacementNonce != null) {
27803
27816
  const successor = client.quote.replacement(item.id, BigInt(state.replacementNonce.toString())).nextId;
@@ -27807,7 +27820,7 @@ async function cleanupQuotes(client, options = {}) {
27807
27820
  const info = await client.options.connection.getAccountInfo(item.id, "confirmed");
27808
27821
  if (!info)
27809
27822
  continue;
27810
- transaction.add(await (owner ? client.program.methods.closeQuote().accountsPartial({ maker: actor, quote: item.id }) : client.program.methods.closeOperatorQuote().accountsPartial({ operator: actor, quote: item.id })).instruction());
27823
+ transaction.add(await (owner2 ? client.program.methods.closeQuote().accountsPartial({ maker: actor, quote: item.id }) : client.program.methods.closeOperatorQuote().accountsPartial({ operator: actor, quote: item.id })).instruction());
27811
27824
  reclaimedLamports += BigInt(info.lamports);
27812
27825
  if (++closed === limit)
27813
27826
  break;
@@ -27824,7 +27837,7 @@ async function cleanupQuotes(client, options = {}) {
27824
27837
 
27825
27838
  // src/client/client.ts
27826
27839
  function pda(seeds) {
27827
- return PublicKey7.findProgramAddressSync(seeds, DEPTH_PROGRAM_ID4)[0];
27840
+ return PublicKey8.findProgramAddressSync(seeds, DEPTH_PROGRAM_ID4)[0];
27828
27841
  }
27829
27842
  function u642(value, name2) {
27830
27843
  if (typeof value !== "bigint" || value < 0n || value > (1n << 64n) - 1n) {
@@ -27898,7 +27911,7 @@ class MakerClient {
27898
27911
  this.backstop = Object.assign((id) => new BackstopHandle(id, this), { offer: (input) => BackstopHandle.offer(this, input) });
27899
27912
  }
27900
27913
  static async create(options) {
27901
- const appId = typeof options.appId === "string" ? new PublicKey7(options.appId) : options.appId;
27914
+ const appId = typeof options.appId === "string" ? new PublicKey8(options.appId) : options.appId;
27902
27915
  if (options.maker && options.makerOwner && !options.maker.equals(options.makerOwner)) {
27903
27916
  throw new Error("maker and makerOwner identify different accounts");
27904
27917
  }
@@ -27944,7 +27957,7 @@ class MakerClient {
27944
27957
  summary: async () => (await this.owned()).account.summary(),
27945
27958
  pending: async () => (await this.owned()).account.pending(),
27946
27959
  deposit: async (sourceOrAmount, amount3, clientNonce) => {
27947
- if (sourceOrAmount instanceof PublicKey7) {
27960
+ if (sourceOrAmount instanceof PublicKey8) {
27948
27961
  if (amount3 === undefined || clientNonce === undefined)
27949
27962
  throw new RangeError("Source deposits require amount and nonce");
27950
27963
  return this.funding.deposit(sourceOrAmount, amount3, clientNonce);
@@ -27954,7 +27967,7 @@ class MakerClient {
27954
27967
  withdrawalHandle: (clientNonce) => this.funding.withdrawalHandle(clientNonce),
27955
27968
  depositHandle: (nonce) => this.funding.depositHandle(nonce),
27956
27969
  withdraw: async (destinationOrAmount, amount3, clientNonce) => {
27957
- if (destinationOrAmount instanceof PublicKey7) {
27970
+ if (destinationOrAmount instanceof PublicKey8) {
27958
27971
  if (amount3 === undefined || clientNonce === undefined)
27959
27972
  throw new RangeError("Destination withdrawals require amount and nonce");
27960
27973
  return this.funding.withdraw(destinationOrAmount, amount3, clientNonce);
@@ -28104,8 +28117,9 @@ class MakerClient {
28104
28117
  await new Promise((resolve) => setTimeout(resolve, 1000));
28105
28118
  }
28106
28119
  const market = await this.read("Market", this.marketAddress);
28107
- const input = "tiers" in request ? request : this.flatQuote(request, market, clientNonce);
28108
- this.validateQuote(input, market);
28120
+ const time = await chainTime(this.options.connection);
28121
+ const input = "tiers" in request ? request : this.flatQuote(request, market, clientNonce, time);
28122
+ this.validateQuote(input, market, time);
28109
28123
  const grant = await this.grantFor(input.maxCapacity, input.expiresAt);
28110
28124
  const mxePublicKey = await getMXEPublicKey(this.provider, DEPTH_PROGRAM_ID4);
28111
28125
  if (!mxePublicKey)
@@ -28147,7 +28161,7 @@ class MakerClient {
28147
28161
  const limit = options.limit ?? 20;
28148
28162
  if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100)
28149
28163
  throw new RangeError("limit must be an integer from 1 to 100");
28150
- const cursor = options.cursor ? new PublicKey7(options.cursor).toBase58() : undefined;
28164
+ const cursor = options.cursor ? new PublicKey8(options.cursor).toBase58() : undefined;
28151
28165
  const accounts2 = await this.options.connection.getProgramAccounts(DEPTH_PROGRAM_ID4, {
28152
28166
  commitment: "confirmed",
28153
28167
  dataSlice: { offset: 0, length: 0 },
@@ -28213,7 +28227,7 @@ class MakerClient {
28213
28227
  return this.quote.replacement(prior, nonce).resume(input, options);
28214
28228
  }
28215
28229
  };
28216
- flatQuote(input, market, clientNonce) {
28230
+ flatQuote(input, market, clientNonce, now) {
28217
28231
  if (input.marketId !== SOL_USD_MARKET_ID2)
28218
28232
  throw new Error("Only SOL-USD is supported");
28219
28233
  if (!Number.isSafeInteger(input.validForSeconds) || input.validForSeconds < 1 || input.validForSeconds > market.config.maxQuoteLifetimeSeconds)
@@ -28228,7 +28242,7 @@ class MakerClient {
28228
28242
  return {
28229
28243
  tiers,
28230
28244
  maxCapacity: input.maxCapacity,
28231
- expiresAt: Math.floor(Date.now() / 1000) + input.validForSeconds,
28245
+ expiresAt: now + input.validForSeconds,
28232
28246
  clientNonce
28233
28247
  };
28234
28248
  }
@@ -28351,7 +28365,7 @@ class MakerClient {
28351
28365
  const store = createCursorStore(genesis, this.options.maker.toBase58(), this.options.cursorDirectory);
28352
28366
  const cursor = await store.load();
28353
28367
  return new FillHistory(this).subscribe(async (fill) => {
28354
- const execution = await this.fills.get(new PublicKey7(fill.id));
28368
+ const execution = await this.fills.get(new PublicKey8(fill.id));
28355
28369
  if (execution.status !== "ready")
28356
28370
  throw new Error(`Private maker fill ${fill.id} is ${execution.status}`);
28357
28371
  await options.onFill({ ...fill, execution });
@@ -28370,8 +28384,7 @@ class MakerClient {
28370
28384
  return result;
28371
28385
  }
28372
28386
  };
28373
- validateQuote(input, market) {
28374
- const now = Math.floor(Date.now() / 1000);
28387
+ validateQuote(input, market, now) {
28375
28388
  const reference = BigInt(market.oraclePriceE8.toString());
28376
28389
  const { preparationBps } = quoteBounds4(market.config.maxQuoteDeviationBps);
28377
28390
  if (!["draft", "active"].includes(stateName(market.status)))
@@ -28409,13 +28422,40 @@ async function createDepthMaker(options) {
28409
28422
  return MakerClient.create(options);
28410
28423
  }
28411
28424
  // src/hedge/wallet.ts
28412
- async function connect_hyperliquid(provider) {
28413
- const accounts2 = await provider.request({ method: "eth_requestAccounts" });
28425
+ async function connect_hyperliquid(provider, options = {}) {
28426
+ const timeoutMs = options.timeoutMs ?? 60000;
28427
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 300000)
28428
+ throw new RangeError("Wallet timeout must be 1 to 300000 milliseconds");
28429
+ const request = (input) => new Promise((resolve, reject) => {
28430
+ const signal = options.signal;
28431
+ if (signal?.aborted) {
28432
+ reject(new Error("Wallet connection cancelled"));
28433
+ return;
28434
+ }
28435
+ let settled = false;
28436
+ const finish = (action) => {
28437
+ if (settled)
28438
+ return;
28439
+ settled = true;
28440
+ clearTimeout(timer);
28441
+ signal?.removeEventListener("abort", abort);
28442
+ action();
28443
+ };
28444
+ const abort = () => finish(() => reject(new Error("Wallet connection cancelled")));
28445
+ const timer = setTimeout(() => finish(() => reject(new Error("Wallet did not respond. Open the extension and resolve its pending request."))), timeoutMs);
28446
+ signal?.addEventListener("abort", abort, { once: true });
28447
+ try {
28448
+ provider.request(input).then((value) => finish(() => resolve(value)), (cause) => finish(() => reject(cause)));
28449
+ } catch (cause) {
28450
+ finish(() => reject(cause));
28451
+ }
28452
+ });
28453
+ const accounts2 = await request({ method: "eth_requestAccounts" });
28414
28454
  if (!Array.isArray(accounts2) || typeof accounts2[0] !== "string" || !/^0x[0-9a-fA-F]{40}$/.test(accounts2[0]))
28415
28455
  throw new Error("The Ethereum wallet did not provide an account");
28416
28456
  const address3 = accounts2[0];
28417
28457
  const check = async () => {
28418
- const current = await provider.request({ method: "eth_accounts" });
28458
+ const current = await request({ method: "eth_accounts" });
28419
28459
  if (!Array.isArray(current) || typeof current[0] !== "string" || current[0].toLowerCase() !== address3.toLowerCase())
28420
28460
  throw new Error("The Ethereum wallet account changed; connect it again");
28421
28461
  };
@@ -28425,14 +28465,14 @@ async function connect_hyperliquid(provider) {
28425
28465
  return [address3];
28426
28466
  },
28427
28467
  async getChainId() {
28428
- const chain = await provider.request({ method: "eth_chainId" });
28468
+ const chain = await request({ method: "eth_chainId" });
28429
28469
  if (typeof chain !== "string" || !/^0x[0-9a-fA-F]+$/.test(chain) || !Number.isSafeInteger(Number(chain)))
28430
28470
  throw new Error("Ethereum wallet chain is invalid");
28431
28471
  return Number(chain);
28432
28472
  },
28433
28473
  async signTypedData(params) {
28434
28474
  await check();
28435
- const signature = await provider.request({
28475
+ const signature = await request({
28436
28476
  method: "eth_signTypedData_v4",
28437
28477
  params: [
28438
28478
  address3,
@@ -1,5 +1,20 @@
1
1
  // src/client/client.ts
2
2
  import { Buffer as Buffer8 } from "buffer";
3
+
4
+ // src/chain/clock.ts
5
+ import { PublicKey, SYSVAR_CLOCK_PUBKEY } from "@solana/web3.js";
6
+ var owner = new PublicKey("Sysvar1111111111111111111111111111111111111");
7
+ async function chainTime(connection) {
8
+ const clock = await connection.getAccountInfo(SYSVAR_CLOCK_PUBKEY, "confirmed");
9
+ if (!clock?.owner.equals(owner) || clock.data.length !== 40)
10
+ throw new Error("Solana clock sysvar is unavailable");
11
+ const time = clock.data.readBigInt64LE(32);
12
+ if (time <= 0n || time > BigInt(Number.MAX_SAFE_INTEGER))
13
+ throw new Error("Solana clock timestamp is invalid");
14
+ return Number(time);
15
+ }
16
+
17
+ // src/client/client.ts
3
18
  import * as anchor6 from "@anchor-lang/core";
4
19
 
5
20
  // ../../node_modules/@arcium-hq/client/build/index.mjs
@@ -2233,7 +2248,7 @@ import * as anchor from "@anchor-lang/core";
2233
2248
  import { Buffer } from "buffer";
2234
2249
  import * as anchor__default from "@anchor-lang/core";
2235
2250
  import { BorshCoder, convertIdlToCamelCase, Program } from "@anchor-lang/core";
2236
- import { AddressLookupTableProgram, PublicKey } from "@solana/web3.js";
2251
+ import { AddressLookupTableProgram, PublicKey as PublicKey2 } from "@solana/web3.js";
2237
2252
  var CURVE25519_SCALAR_FIELD_MODULUS = ed25519.CURVE.n;
2238
2253
  function serializeLE(val, lengthInBytes) {
2239
2254
  const result = new Uint8Array(lengthInBytes);
@@ -24840,7 +24855,7 @@ function findArciumPda(seeds) {
24840
24855
  return findPda(getArciumProgramId(), seeds);
24841
24856
  }
24842
24857
  function findPda(programId, seeds) {
24843
- return PublicKey.findProgramAddressSync(seeds, programId)[0];
24858
+ return PublicKey2.findProgramAddressSync(seeds, programId)[0];
24844
24859
  }
24845
24860
  function assertIntegerInRange(value, min, max, label) {
24846
24861
  if (!Number.isInteger(value) || value < min || value > max) {
@@ -24912,7 +24927,7 @@ import {
24912
24927
  } from "@depthhq/bindings";
24913
24928
  import { DepthView } from "@depthhq/sdk/views";
24914
24929
  import { DepthRecovery } from "@depthhq/sdk/recovery";
24915
- import { PublicKey as PublicKey7 } from "@solana/web3.js";
24930
+ import { PublicKey as PublicKey8 } from "@solana/web3.js";
24916
24931
 
24917
24932
  // src/chain/random.ts
24918
24933
  function randomBytes3(length) {
@@ -24935,8 +24950,8 @@ function order(value, receipt = false) {
24935
24950
  return true;
24936
24951
  return ["filled", "cancelled", "rejected"].includes(String(value.status)) && integer(value.completedAt) && value.completedAt >= value.nonce && amount(value.filledSize) && BigInt(value.filledSize) >= 0n && BigInt(value.filledSize) <= BigInt(value.size) && (value.filledAt === undefined || integer(value.filledAt) && value.filledAt >= value.nonce - 1000 && value.filledAt <= value.completedAt + 1000) && optional(value.orderId, integer) && optional(value.averagePriceE8, amount) && optional(value.fee, amount) && optional(value.reason, text) && optional(value.hashes, (hashes) => Array.isArray(hashes) && hashes.length <= 2000 && hashes.every((hash) => typeof hash === "string" && /^0x[0-9a-fA-F]{64}$/.test(hash)));
24937
24952
  }
24938
- function hedgeView(value, owner) {
24939
- if (!object(value) || value.maker !== owner || typeof value.enabled !== "boolean")
24953
+ function hedgeView(value, owner2) {
24954
+ if (!object(value) || value.maker !== owner2 || typeof value.enabled !== "boolean")
24940
24955
  throw new Error("Maker hedge response belongs to another wallet or is invalid");
24941
24956
  const snapshot = value.snapshot;
24942
24957
  if (snapshot !== null && (!value.enabled || !object(snapshot) || snapshot.venue !== "hyperliquid" || !["testnet", "mainnet"].includes(String(snapshot.network)) || typeof snapshot.account !== "string" || !/^0x[0-9a-fA-F]{40}$/.test(snapshot.account) || !integer(snapshot.updatedAt) || !["balanced", "waiting", "pending", "blocked"].includes(String(snapshot.phase)) || !optional(snapshot.reason, text) || !optional(snapshot.depthPosition, amount) || !optional(snapshot.externalPosition, amount) || !optional(snapshot.unhedgedPosition, amount) || !optional(snapshot.pending, order) || !Array.isArray(snapshot.history) || snapshot.history.length > 50 || !snapshot.history.every((record) => order(record, true))))
@@ -25027,7 +25042,7 @@ class MakerAccess extends WalletSession {
25027
25042
  }
25028
25043
 
25029
25044
  // src/automation/automation.ts
25030
- import { PublicKey as PublicKey3, SystemProgram, Transaction } from "@solana/web3.js";
25045
+ import { PublicKey as PublicKey4, SystemProgram, Transaction } from "@solana/web3.js";
25031
25046
  import { Buffer as Buffer3 } from "buffer";
25032
25047
 
25033
25048
  // src/hedge/setup.ts
@@ -25115,7 +25130,7 @@ async function setup_hyperliquid(options) {
25115
25130
  }
25116
25131
 
25117
25132
  // src/automation/types.ts
25118
- import { PublicKey as PublicKey2 } from "@solana/web3.js";
25133
+ import { PublicKey as PublicKey3 } from "@solana/web3.js";
25119
25134
 
25120
25135
  // src/bot/policy.ts
25121
25136
  import { positionPnl } from "@depthhq/sdk";
@@ -25248,7 +25263,7 @@ function automationConfig(value) {
25248
25263
  };
25249
25264
  }
25250
25265
  var automationWire = (config) => JSON.parse(JSON.stringify(config, (_key, value) => typeof value === "bigint" ? value.toString() : value));
25251
- function automationView(value, owner, environment) {
25266
+ function automationView(value, owner2, environment) {
25252
25267
  if (value === null)
25253
25268
  return null;
25254
25269
  const view = object2(value, [
@@ -25266,9 +25281,9 @@ function automationView(value, owner, environment) {
25266
25281
  "hedge",
25267
25282
  "funding"
25268
25283
  ]);
25269
- if (view.owner !== owner || view.environment !== environment)
25284
+ if (view.owner !== owner2 || view.environment !== environment)
25270
25285
  throw new Error("Automation belongs to another wallet or network");
25271
- if (typeof view.operator !== "string" || new PublicKey2(view.operator).toBase58() !== view.operator || typeof view.agent !== "string" || !/^0x[0-9a-fA-F]{40}$/.test(view.agent) || integer2(view.revision) < 1 || integer2(view.updatedAt) < 1 || !["running", "paused"].includes(String(view.desired)) || ![
25286
+ if (typeof view.operator !== "string" || new PublicKey3(view.operator).toBase58() !== view.operator || typeof view.agent !== "string" || !/^0x[0-9a-fA-F]{40}$/.test(view.agent) || integer2(view.revision) < 1 || integer2(view.updatedAt) < 1 || !["running", "paused"].includes(String(view.desired)) || ![
25272
25287
  "configured",
25273
25288
  "starting",
25274
25289
  "running",
@@ -25280,10 +25295,10 @@ function automationView(value, owner, environment) {
25280
25295
  automationConfig(view.config);
25281
25296
  if (view.bot !== undefined) {
25282
25297
  const bot = object2(view.bot, ["phase", "reason", "quoteId", "fillCount"]);
25283
- if (!["active", "pending", "idle", "stopped"].includes(String(bot.phase)) || integer2(bot.fillCount) < 0 || bot.reason !== undefined && typeof bot.reason !== "string" || bot.quoteId !== undefined && (typeof bot.quoteId !== "string" || new PublicKey2(bot.quoteId).toBase58() !== bot.quoteId))
25298
+ if (!["active", "pending", "idle", "stopped"].includes(String(bot.phase)) || integer2(bot.fillCount) < 0 || bot.reason !== undefined && typeof bot.reason !== "string" || bot.quoteId !== undefined && (typeof bot.quoteId !== "string" || new PublicKey3(bot.quoteId).toBase58() !== bot.quoteId))
25284
25299
  throw new Error("Automation status is invalid");
25285
25300
  }
25286
- hedgeView(view.hedge, owner);
25301
+ hedgeView(view.hedge, owner2);
25287
25302
  if (view.funding !== undefined) {
25288
25303
  const funding = object2(view.funding, ["signature", "lamports", "status"]);
25289
25304
  if (typeof funding.signature !== "string" || !/^[1-9A-HJ-NP-Za-km-z]{64,90}$/.test(funding.signature) || !["pending", "confirmed", "expired", "failed"].includes(String(funding.status)))
@@ -25355,7 +25370,7 @@ class MakerAutomation extends WalletSession {
25355
25370
  maxLeverage: config.hedge.policy.maxLeverage
25356
25371
  });
25357
25372
  }
25358
- const operator = new PublicKey3(profile.operator);
25373
+ const operator = new PublicKey4(profile.operator);
25359
25374
  const balance = BigInt(await this.maker.options.connection.getBalance(operator, "confirmed"));
25360
25375
  const signatures = [];
25361
25376
  const recovered = this.view(await this.authorizedRequest("fund", { revision: current.revision }));
@@ -25469,7 +25484,7 @@ import { Buffer as Buffer5 } from "buffer";
25469
25484
  import * as anchor2 from "@anchor-lang/core";
25470
25485
  import { DEPTH_PROGRAM_ID as DEPTH_PROGRAM_ID2, SOL_USD_MARKET_ID } from "@depthhq/bindings";
25471
25486
  import { DepthError } from "@depthhq/sdk";
25472
- import { PublicKey as PublicKey4 } from "@solana/web3.js";
25487
+ import { PublicKey as PublicKey5 } from "@solana/web3.js";
25473
25488
  function u64(value, name) {
25474
25489
  if (typeof value !== "bigint" || value < 0n || value > (1n << 64n) - 1n)
25475
25490
  throw new RangeError(`${name} must be an unsigned 64-bit bigint`);
@@ -25486,7 +25501,7 @@ function timing(options) {
25486
25501
  }
25487
25502
  function address2(market, maker, nonce) {
25488
25503
  const bytes = u64(nonce, "clientNonce").toArrayLike(Buffer5, "le", 8);
25489
- return PublicKey4.findProgramAddressSync([Buffer5.from("backstop"), market.toBuffer(), maker.toBuffer(), bytes], DEPTH_PROGRAM_ID2)[0];
25504
+ return PublicKey5.findProgramAddressSync([Buffer5.from("backstop"), market.toBuffer(), maker.toBuffer(), bytes], DEPTH_PROGRAM_ID2)[0];
25490
25505
  }
25491
25506
  async function poll(read, terminal, options) {
25492
25507
  const { timeoutMs, pollMs } = timing(options);
@@ -25528,7 +25543,7 @@ class BackstopHandle {
25528
25543
  ]);
25529
25544
  const now = Math.floor(Date.now() / 1000);
25530
25545
  const margin = (input.maxCapacity * BigInt(market.config.initialMarginBps) + 9999n) / 10000n;
25531
- if (!market.backstopOffer.equals(PublicKey4.default) || market.pendingOrders !== 0 || Object.keys(market.status)[0] === "settling" || input.maxCapacity > BigInt(market.config.maxAccountNotional.toString()) || margin < BigInt(market.config.backstopMinCollateral.toString()))
25546
+ if (!market.backstopOffer.equals(PublicKey5.default) || market.pendingOrders !== 0 || Object.keys(market.status)[0] === "settling" || input.maxCapacity > BigInt(market.config.maxAccountNotional.toString()) || margin < BigInt(market.config.backstopMinCollateral.toString()))
25532
25547
  throw new Error("Backstop offer is outside the current market policy");
25533
25548
  if (!account.owner.equals(client.options.maker) || !account.market.equals(client.marketAddress) || !("ready" in account.status))
25534
25549
  throw new Error("Maker market account is unavailable");
@@ -25631,7 +25646,7 @@ class BackstopRelease {
25631
25646
  // src/fill/history.ts
25632
25647
  import { Buffer as Buffer6 } from "buffer";
25633
25648
  import * as anchor3 from "@anchor-lang/core";
25634
- import { PublicKey as PublicKey5 } from "@solana/web3.js";
25649
+ import { PublicKey as PublicKey6 } from "@solana/web3.js";
25635
25650
 
25636
25651
  class FillHistory {
25637
25652
  client;
@@ -25694,7 +25709,7 @@ class FillHistory {
25694
25709
  const fills = [];
25695
25710
  for (let i = 0;i < addresses.length; i += 100) {
25696
25711
  const batch = addresses.slice(i, i + 100);
25697
- const accounts2 = await connection.getMultipleAccountsInfo(batch.map((id) => new PublicKey5(id)), "finalized");
25712
+ const accounts2 = await connection.getMultipleAccountsInfo(batch.map((id) => new PublicKey6(id)), "finalized");
25698
25713
  accounts2.forEach((info, index) => {
25699
25714
  if (!info || !info.owner.equals(this.client.program.programId))
25700
25715
  throw new Error("Finalized fill receipt is unavailable");
@@ -25840,7 +25855,7 @@ class QuoteHandle {
25840
25855
  const market = await this.client.read("Market", this.client.marketAddress);
25841
25856
  if (!Number.isSafeInteger(input.validForSeconds) || input.validForSeconds < 1 || input.validForSeconds > market.config.maxQuoteLifetimeSeconds)
25842
25857
  throw new RangeError("Quote duration is outside market policy");
25843
- const expiry = Math.floor(Date.now() / 1000) + input.validForSeconds;
25858
+ const expiry = await chainTime(this.client.options.connection) + input.validForSeconds;
25844
25859
  const before = await this.status();
25845
25860
  if (before.status !== "active")
25846
25861
  throw new Error("Only active quotes can be renewed");
@@ -26106,7 +26121,7 @@ class QuoteHandle {
26106
26121
  // src/quote/replacement.ts
26107
26122
  import * as anchor5 from "@anchor-lang/core";
26108
26123
  import { DEPTH_PROGRAM_ID as DEPTH_PROGRAM_ID3 } from "@depthhq/bindings";
26109
- import { PublicKey as PublicKey6 } from "@solana/web3.js";
26124
+ import { PublicKey as PublicKey7 } from "@solana/web3.js";
26110
26125
  import { Buffer as Buffer7 } from "buffer";
26111
26126
  import { quoteBounds as quoteBounds3 } from "@depthhq/bindings";
26112
26127
 
@@ -26123,7 +26138,7 @@ class QuoteReplacement {
26123
26138
  throw new RangeError("clientNonce must be an unsigned 64-bit bigint");
26124
26139
  }
26125
26140
  const bytes = new anchor5.BN(nonce.toString()).toArrayLike(Buffer7, "le", 8);
26126
- this.nextId = PublicKey6.findProgramAddressSync([
26141
+ this.nextId = PublicKey7.findProgramAddressSync([
26127
26142
  Buffer7.from("quote"),
26128
26143
  client.marketAddress.toBuffer(),
26129
26144
  client.options.maker.toBuffer(),
@@ -26306,7 +26321,7 @@ async function cleanupQuotes(client, options = {}) {
26306
26321
  throw new RangeError("Cleanup limit must be between 1 and 12");
26307
26322
  const excluded = new Set(options.exclude);
26308
26323
  const actor = client.options.wallet.publicKey;
26309
- const owner = actor.equals(client.options.maker);
26324
+ const owner2 = actor.equals(client.options.maker);
26310
26325
  const transaction = new Transaction2().add(ComputeBudgetProgram.setComputeUnitLimit({ units: 400000 }));
26311
26326
  let closed = 0;
26312
26327
  let reclaimedLamports = 0n;
@@ -26317,7 +26332,7 @@ async function cleanupQuotes(client, options = {}) {
26317
26332
  if (excluded.has(item.id.toBase58()))
26318
26333
  continue;
26319
26334
  const state = await client.quoteState(item.id);
26320
- if (!["cancelled", "filled", "rejected"].includes(Object.keys(state.status)[0]) || state.attachedOrders !== 0 || !owner && !state.operator.equals(actor))
26335
+ if (!["cancelled", "filled", "rejected"].includes(Object.keys(state.status)[0]) || state.attachedOrders !== 0 || !owner2 && !state.operator.equals(actor))
26321
26336
  continue;
26322
26337
  if (state.replacementNonce != null) {
26323
26338
  const successor = client.quote.replacement(item.id, BigInt(state.replacementNonce.toString())).nextId;
@@ -26327,7 +26342,7 @@ async function cleanupQuotes(client, options = {}) {
26327
26342
  const info = await client.options.connection.getAccountInfo(item.id, "confirmed");
26328
26343
  if (!info)
26329
26344
  continue;
26330
- transaction.add(await (owner ? client.program.methods.closeQuote().accountsPartial({ maker: actor, quote: item.id }) : client.program.methods.closeOperatorQuote().accountsPartial({ operator: actor, quote: item.id })).instruction());
26345
+ transaction.add(await (owner2 ? client.program.methods.closeQuote().accountsPartial({ maker: actor, quote: item.id }) : client.program.methods.closeOperatorQuote().accountsPartial({ operator: actor, quote: item.id })).instruction());
26331
26346
  reclaimedLamports += BigInt(info.lamports);
26332
26347
  if (++closed === limit)
26333
26348
  break;
@@ -26344,7 +26359,7 @@ async function cleanupQuotes(client, options = {}) {
26344
26359
 
26345
26360
  // src/client/client.ts
26346
26361
  function pda(seeds) {
26347
- return PublicKey7.findProgramAddressSync(seeds, DEPTH_PROGRAM_ID4)[0];
26362
+ return PublicKey8.findProgramAddressSync(seeds, DEPTH_PROGRAM_ID4)[0];
26348
26363
  }
26349
26364
  function u642(value, name2) {
26350
26365
  if (typeof value !== "bigint" || value < 0n || value > (1n << 64n) - 1n) {
@@ -26418,7 +26433,7 @@ class MakerClient {
26418
26433
  this.backstop = Object.assign((id) => new BackstopHandle(id, this), { offer: (input) => BackstopHandle.offer(this, input) });
26419
26434
  }
26420
26435
  static async create(options) {
26421
- const appId = typeof options.appId === "string" ? new PublicKey7(options.appId) : options.appId;
26436
+ const appId = typeof options.appId === "string" ? new PublicKey8(options.appId) : options.appId;
26422
26437
  if (options.maker && options.makerOwner && !options.maker.equals(options.makerOwner)) {
26423
26438
  throw new Error("maker and makerOwner identify different accounts");
26424
26439
  }
@@ -26464,7 +26479,7 @@ class MakerClient {
26464
26479
  summary: async () => (await this.owned()).account.summary(),
26465
26480
  pending: async () => (await this.owned()).account.pending(),
26466
26481
  deposit: async (sourceOrAmount, amount3, clientNonce) => {
26467
- if (sourceOrAmount instanceof PublicKey7) {
26482
+ if (sourceOrAmount instanceof PublicKey8) {
26468
26483
  if (amount3 === undefined || clientNonce === undefined)
26469
26484
  throw new RangeError("Source deposits require amount and nonce");
26470
26485
  return this.funding.deposit(sourceOrAmount, amount3, clientNonce);
@@ -26474,7 +26489,7 @@ class MakerClient {
26474
26489
  withdrawalHandle: (clientNonce) => this.funding.withdrawalHandle(clientNonce),
26475
26490
  depositHandle: (nonce) => this.funding.depositHandle(nonce),
26476
26491
  withdraw: async (destinationOrAmount, amount3, clientNonce) => {
26477
- if (destinationOrAmount instanceof PublicKey7) {
26492
+ if (destinationOrAmount instanceof PublicKey8) {
26478
26493
  if (amount3 === undefined || clientNonce === undefined)
26479
26494
  throw new RangeError("Destination withdrawals require amount and nonce");
26480
26495
  return this.funding.withdraw(destinationOrAmount, amount3, clientNonce);
@@ -26624,8 +26639,9 @@ class MakerClient {
26624
26639
  await new Promise((resolve) => setTimeout(resolve, 1000));
26625
26640
  }
26626
26641
  const market = await this.read("Market", this.marketAddress);
26627
- const input = "tiers" in request ? request : this.flatQuote(request, market, clientNonce);
26628
- this.validateQuote(input, market);
26642
+ const time = await chainTime(this.options.connection);
26643
+ const input = "tiers" in request ? request : this.flatQuote(request, market, clientNonce, time);
26644
+ this.validateQuote(input, market, time);
26629
26645
  const grant = await this.grantFor(input.maxCapacity, input.expiresAt);
26630
26646
  const mxePublicKey = await getMXEPublicKey(this.provider, DEPTH_PROGRAM_ID4);
26631
26647
  if (!mxePublicKey)
@@ -26667,7 +26683,7 @@ class MakerClient {
26667
26683
  const limit = options.limit ?? 20;
26668
26684
  if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100)
26669
26685
  throw new RangeError("limit must be an integer from 1 to 100");
26670
- const cursor = options.cursor ? new PublicKey7(options.cursor).toBase58() : undefined;
26686
+ const cursor = options.cursor ? new PublicKey8(options.cursor).toBase58() : undefined;
26671
26687
  const accounts2 = await this.options.connection.getProgramAccounts(DEPTH_PROGRAM_ID4, {
26672
26688
  commitment: "confirmed",
26673
26689
  dataSlice: { offset: 0, length: 0 },
@@ -26733,7 +26749,7 @@ class MakerClient {
26733
26749
  return this.quote.replacement(prior, nonce).resume(input, options);
26734
26750
  }
26735
26751
  };
26736
- flatQuote(input, market, clientNonce) {
26752
+ flatQuote(input, market, clientNonce, now) {
26737
26753
  if (input.marketId !== SOL_USD_MARKET_ID2)
26738
26754
  throw new Error("Only SOL-USD is supported");
26739
26755
  if (!Number.isSafeInteger(input.validForSeconds) || input.validForSeconds < 1 || input.validForSeconds > market.config.maxQuoteLifetimeSeconds)
@@ -26748,7 +26764,7 @@ class MakerClient {
26748
26764
  return {
26749
26765
  tiers,
26750
26766
  maxCapacity: input.maxCapacity,
26751
- expiresAt: Math.floor(Date.now() / 1000) + input.validForSeconds,
26767
+ expiresAt: now + input.validForSeconds,
26752
26768
  clientNonce
26753
26769
  };
26754
26770
  }
@@ -26871,7 +26887,7 @@ class MakerClient {
26871
26887
  const store = createCursorStore(genesis, this.options.maker.toBase58(), this.options.cursorDirectory);
26872
26888
  const cursor = await store.load();
26873
26889
  return new FillHistory(this).subscribe(async (fill) => {
26874
- const execution = await this.fills.get(new PublicKey7(fill.id));
26890
+ const execution = await this.fills.get(new PublicKey8(fill.id));
26875
26891
  if (execution.status !== "ready")
26876
26892
  throw new Error(`Private maker fill ${fill.id} is ${execution.status}`);
26877
26893
  await options.onFill({ ...fill, execution });
@@ -26890,8 +26906,7 @@ class MakerClient {
26890
26906
  return result;
26891
26907
  }
26892
26908
  };
26893
- validateQuote(input, market) {
26894
- const now = Math.floor(Date.now() / 1000);
26909
+ validateQuote(input, market, now) {
26895
26910
  const reference = BigInt(market.oraclePriceE8.toString());
26896
26911
  const { preparationBps } = quoteBounds4(market.config.maxQuoteDeviationBps);
26897
26912
  if (!["draft", "active"].includes(stateName(market.status)))
@@ -26929,13 +26944,40 @@ async function createDepthMaker(options) {
26929
26944
  return MakerClient.create(options);
26930
26945
  }
26931
26946
  // src/hedge/wallet.ts
26932
- async function connect_hyperliquid(provider) {
26933
- const accounts2 = await provider.request({ method: "eth_requestAccounts" });
26947
+ async function connect_hyperliquid(provider, options = {}) {
26948
+ const timeoutMs = options.timeoutMs ?? 60000;
26949
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 300000)
26950
+ throw new RangeError("Wallet timeout must be 1 to 300000 milliseconds");
26951
+ const request = (input) => new Promise((resolve, reject) => {
26952
+ const signal = options.signal;
26953
+ if (signal?.aborted) {
26954
+ reject(new Error("Wallet connection cancelled"));
26955
+ return;
26956
+ }
26957
+ let settled = false;
26958
+ const finish = (action) => {
26959
+ if (settled)
26960
+ return;
26961
+ settled = true;
26962
+ clearTimeout(timer);
26963
+ signal?.removeEventListener("abort", abort);
26964
+ action();
26965
+ };
26966
+ const abort = () => finish(() => reject(new Error("Wallet connection cancelled")));
26967
+ const timer = setTimeout(() => finish(() => reject(new Error("Wallet did not respond. Open the extension and resolve its pending request."))), timeoutMs);
26968
+ signal?.addEventListener("abort", abort, { once: true });
26969
+ try {
26970
+ provider.request(input).then((value) => finish(() => resolve(value)), (cause) => finish(() => reject(cause)));
26971
+ } catch (cause) {
26972
+ finish(() => reject(cause));
26973
+ }
26974
+ });
26975
+ const accounts2 = await request({ method: "eth_requestAccounts" });
26934
26976
  if (!Array.isArray(accounts2) || typeof accounts2[0] !== "string" || !/^0x[0-9a-fA-F]{40}$/.test(accounts2[0]))
26935
26977
  throw new Error("The Ethereum wallet did not provide an account");
26936
26978
  const address3 = accounts2[0];
26937
26979
  const check = async () => {
26938
- const current = await provider.request({ method: "eth_accounts" });
26980
+ const current = await request({ method: "eth_accounts" });
26939
26981
  if (!Array.isArray(current) || typeof current[0] !== "string" || current[0].toLowerCase() !== address3.toLowerCase())
26940
26982
  throw new Error("The Ethereum wallet account changed; connect it again");
26941
26983
  };
@@ -26945,14 +26987,14 @@ async function connect_hyperliquid(provider) {
26945
26987
  return [address3];
26946
26988
  },
26947
26989
  async getChainId() {
26948
- const chain = await provider.request({ method: "eth_chainId" });
26990
+ const chain = await request({ method: "eth_chainId" });
26949
26991
  if (typeof chain !== "string" || !/^0x[0-9a-fA-F]+$/.test(chain) || !Number.isSafeInteger(Number(chain)))
26950
26992
  throw new Error("Ethereum wallet chain is invalid");
26951
26993
  return Number(chain);
26952
26994
  },
26953
26995
  async signTypedData(params) {
26954
26996
  await check();
26955
- const signature = await provider.request({
26997
+ const signature = await request({
26956
26998
  method: "eth_signTypedData_v4",
26957
26999
  params: [
26958
27000
  address3,
package/dist/node.js CHANGED
@@ -44,7 +44,7 @@ class FillCursorStore {
44
44
  }
45
45
  // src/bot/bot.ts
46
46
  import { createHash, randomBytes as randomBytes3 } from "node:crypto";
47
- import { PublicKey } from "@solana/web3.js";
47
+ import { PublicKey as PublicKey2 } from "@solana/web3.js";
48
48
  import { DEPTH_PROGRAM_ID } from "@depthhq/bindings";
49
49
 
50
50
  // src/bot/store.ts
@@ -372,6 +372,19 @@ function botQuote(policy, market, position, nonce, now, pricing, reservationCred
372
372
  };
373
373
  }
374
374
 
375
+ // src/chain/clock.ts
376
+ import { PublicKey, SYSVAR_CLOCK_PUBKEY } from "@solana/web3.js";
377
+ var owner = new PublicKey("Sysvar1111111111111111111111111111111111111");
378
+ async function chainTime(connection) {
379
+ const clock = await connection.getAccountInfo(SYSVAR_CLOCK_PUBKEY, "confirmed");
380
+ if (!clock?.owner.equals(owner) || clock.data.length !== 40)
381
+ throw new Error("Solana clock sysvar is unavailable");
382
+ const time = clock.data.readBigInt64LE(32);
383
+ if (time <= 0n || time > BigInt(Number.MAX_SAFE_INTEGER))
384
+ throw new Error("Solana clock timestamp is invalid");
385
+ return Number(time);
386
+ }
387
+
375
388
  // src/bot/bot.ts
376
389
  var terminal = new Set(["filled", "cancelled", "rejected"]);
377
390
  var nonce = () => BigInt(`0x${randomBytes3(8).toString("hex")}`);
@@ -413,7 +426,7 @@ class MakerBot {
413
426
  if (options.hedge) {
414
427
  if (bot.state.hedge && bot.state.hedge.identity !== options.hedge.adapter.identity)
415
428
  throw new Error("Hedge checkpoint belongs to another account or network");
416
- bot.hedgeLease = new BotStore(PublicKey.default.toBase58(), new PublicKey(createHash("sha256").update(options.hedge.adapter.identity).digest()).toBase58(), PublicKey.default.toBase58(), options.stateDirectory);
429
+ bot.hedgeLease = new BotStore(PublicKey2.default.toBase58(), new PublicKey2(createHash("sha256").update(options.hedge.adapter.identity).digest()).toBase58(), PublicKey2.default.toBase58(), options.stateDirectory);
417
430
  await bot.hedgeLease.acquire();
418
431
  const shared = await bot.hedgeLease.load();
419
432
  if (shared.hedge)
@@ -463,18 +476,18 @@ class MakerBot {
463
476
  const ids = [
464
477
  this.state.quote,
465
478
  write?.quote,
466
- this.state.replacement ? this.maker.quote.replacement(new PublicKey(this.state.replacement.prior), this.state.replacement.nonce).nextId.toBase58() : undefined
479
+ this.state.replacement ? this.maker.quote.replacement(new PublicKey2(this.state.replacement.prior), this.state.replacement.nonce).nextId.toBase58() : undefined
467
480
  ];
468
481
  for (const id of new Set(ids.filter((id2) => Boolean(id2)))) {
469
- const quote = await this.maker.quoteState(new PublicKey(id));
470
- if ((("reserving" in quote.status) || ("releasing" in quote.status)) && quote.pendingComputation?.equals(confirmed.pendingComputation ?? PublicKey.default))
482
+ const quote = await this.maker.quoteState(new PublicKey2(id));
483
+ if ((("reserving" in quote.status) || ("releasing" in quote.status)) && quote.pendingComputation?.equals(confirmed.pendingComputation ?? PublicKey2.default))
471
484
  return "updating quote margin";
472
485
  }
473
486
  }
474
487
  let ready = "ready" in account.status && "ready" in confirmed.status && account.version.toString() === confirmed.version.toString();
475
488
  const currentVersion = BigInt(confirmed.version.toString());
476
489
  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;
477
- const completedQuote = write && "ready" in confirmed.status && currentVersion === write.version + 1n && await this.maker.quoteState(new PublicKey(write.quote));
490
+ const completedQuote = write && "ready" in confirmed.status && currentVersion === write.version + 1n && await this.maker.quoteState(new PublicKey2(write.quote));
478
491
  const completedWrite = candidate && completedQuote ? completedQuote.status : undefined;
479
492
  const ownCommit = write && completedWrite && (write.release ? "cancelled" in completedWrite || "filled" in completedWrite : ("active" in completedWrite));
480
493
  const riskWrite = [
@@ -535,7 +548,7 @@ class MakerBot {
535
548
  const market = await this.maker.read("Market", this.maker.marketAddress);
536
549
  const price = BigInt(market.oraclePriceE8.toString());
537
550
  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);
538
- const quote = await this.maker.quoteState(new PublicKey(risk.quote));
551
+ const quote = await this.maker.quoteState(new PublicKey2(risk.quote));
539
552
  const balanceNonce = Buffer.from(quote.balanceNonce).toString("hex");
540
553
  const unchanged = currentVersion === risk.version;
541
554
  const ownFill = "ready" in confirmed.status && currentVersion === risk.version + 1n && risk.balanceNonce !== undefined && balanceNonce !== risk.balanceNonce;
@@ -609,7 +622,7 @@ class MakerBot {
609
622
  return;
610
623
  const bytes = Buffer.alloc(8);
611
624
  bytes.writeBigUInt64LE(value);
612
- return PublicKey.findProgramAddressSync([
625
+ return PublicKey2.findProgramAddressSync([
613
626
  Buffer.from("quote"),
614
627
  this.maker.marketAddress.toBuffer(),
615
628
  this.maker.options.maker.toBuffer(),
@@ -618,7 +631,7 @@ class MakerBot {
618
631
  }
619
632
  async reconcile() {
620
633
  const state = this.state;
621
- if (state.quote && !await this.maker.options.connection.getAccountInfo(new PublicKey(state.quote), "confirmed")) {
634
+ if (state.quote && !await this.maker.options.connection.getAccountInfo(new PublicKey2(state.quote), "confirmed")) {
622
635
  if (!state.request || state.request.expiresAt + 180 < Math.floor(Date.now() / 1000)) {
623
636
  state.quote = undefined;
624
637
  state.request = undefined;
@@ -627,12 +640,27 @@ class MakerBot {
627
640
  }
628
641
  }
629
642
  if (state.replacement) {
630
- const flow = this.maker.quote.replacement(new PublicKey(state.replacement.prior), state.replacement.nonce);
643
+ const flow = this.maker.quote.replacement(new PublicKey2(state.replacement.prior), state.replacement.nonce);
631
644
  const [prior, next] = await Promise.all([
632
- this.maker.options.connection.getAccountInfo(new PublicKey(state.replacement.prior), "confirmed"),
645
+ this.maker.options.connection.getAccountInfo(new PublicKey2(state.replacement.prior), "confirmed"),
633
646
  this.maker.options.connection.getAccountInfo(flow.nextId, "confirmed")
634
647
  ]);
635
- if (!prior && next) {
648
+ const successor = next && await this.maker.quoteState(flow.nextId);
649
+ const predecessor = prior && await this.maker.quoteState(new PublicKey2(state.replacement.prior));
650
+ const completed = predecessor && successor && terminal.has(Object.keys(predecessor.status)[0]) && "active" in successor.status && predecessor.replacementNonce?.toString() === state.replacement.nonce.toString();
651
+ if (successor && (!prior || completed)) {
652
+ if (!successor.operator.equals(this.maker.options.wallet.publicKey) || !state.replacement.request || BigInt(successor.capacity.toString()) !== state.replacement.request.maxCapacity)
653
+ throw new Error("Recovered successor does not match this operator's checkpoint");
654
+ const risk = state.replacement.risk;
655
+ if (risk && "active" in successor.status) {
656
+ const account = await this.maker.read("MarketAccount", this.maker.marketAccount());
657
+ const version = BigInt(account.version.toString());
658
+ if ("ready" in account.status && version === risk.version + 1n) {
659
+ risk.version = version;
660
+ risk.balanceNonce = Buffer.from(successor.balanceNonce).toString("hex");
661
+ state.quoteWrite = undefined;
662
+ }
663
+ }
636
664
  state.quote = flow.nextId.toBase58();
637
665
  state.request = state.replacement.request;
638
666
  state.referencePrice = state.replacement.referencePrice;
@@ -658,14 +686,14 @@ class MakerBot {
658
686
  const allowed = new Set([
659
687
  state.quote,
660
688
  state.replacement?.prior,
661
- state.replacement ? this.maker.quote.replacement(new PublicKey(state.replacement.prior), state.replacement.nonce).nextId.toBase58() : undefined,
689
+ state.replacement ? this.maker.quote.replacement(new PublicKey2(state.replacement.prior), state.replacement.nonce).nextId.toBase58() : undefined,
662
690
  this.requestId()?.toBase58()
663
691
  ].filter(Boolean));
664
692
  for (const id of managed)
665
693
  if (!allowed.has(id)) {
666
- const quote = await this.maker.quote.get(new PublicKey(id)).status();
694
+ const quote = await this.maker.quote.get(new PublicKey2(id)).status();
667
695
  if (["active", "waiting", "validated", "validating", "closing"].includes(quote.status))
668
- await this.maker.quote.get(new PublicKey(id)).cancel();
696
+ await this.maker.quote.get(new PublicKey2(id)).cancel();
669
697
  }
670
698
  }
671
699
  async fills() {
@@ -702,7 +730,7 @@ class MakerBot {
702
730
  const state = this.state;
703
731
  let successorPending = false;
704
732
  if (state.replacement?.request) {
705
- const next = this.maker.quote.replacement(new PublicKey(state.replacement.prior), state.replacement.nonce).nextId;
733
+ const next = this.maker.quote.replacement(new PublicKey2(state.replacement.prior), state.replacement.nonce).nextId;
706
734
  if (await this.maker.options.connection.getAccountInfo(next, "confirmed")) {
707
735
  const handle2 = this.maker.quote.get(next);
708
736
  const snapshot = await handle2.status();
@@ -733,7 +761,7 @@ class MakerBot {
733
761
  return true;
734
762
  }
735
763
  }
736
- const handle = this.maker.quote.get(new PublicKey(state.quote));
764
+ const handle = this.maker.quote.get(new PublicKey2(state.quote));
737
765
  const quote = await handle.status();
738
766
  if (terminal.has(quote.status)) {
739
767
  if (successorPending)
@@ -760,7 +788,7 @@ class MakerBot {
760
788
  async replace(market, now) {
761
789
  const state = this.state;
762
790
  const replacement = state.replacement;
763
- const flow = this.maker.quote.replacement(new PublicKey(replacement.prior), replacement.nonce);
791
+ const flow = this.maker.quote.replacement(new PublicKey2(replacement.prior), replacement.nonce);
764
792
  if (now - market.oracleTimestamp.toNumber() > market.config.maxOracleAgeSeconds - Math.min(Math.floor(market.config.maxOracleAgeSeconds / 2), this.policy.lifetimeSeconds))
765
793
  return this.snapshot("pending", "waiting for a fresh oracle update");
766
794
  if (!replacement.request) {
@@ -779,12 +807,12 @@ class MakerBot {
779
807
  }
780
808
  this.inventoryVersion = this.position.version;
781
809
  this.dirty = false;
782
- const previous2 = await this.maker.quote.get(new PublicKey(replacement.prior)).status();
810
+ const previous2 = await this.maker.quote.get(new PublicKey2(replacement.prior)).status();
783
811
  const policy = this.quotePolicy(market);
784
812
  const planned = botQuote(policy, market, this.position, replacement.nonce, now, this.pricing);
785
813
  let credit = 0n;
786
814
  if (previous2.status === "active" && (!planned || planned.maxCapacity < policy.maxCapacity)) {
787
- this.reservationView ??= this.maker.quote.get(new PublicKey(replacement.prior));
815
+ this.reservationView ??= this.maker.quote.get(new PublicKey2(replacement.prior));
788
816
  const margin = await this.reservationView.reservation();
789
817
  if (margin === undefined)
790
818
  return this.snapshot("pending", "reading remaining quote margin");
@@ -811,7 +839,7 @@ class MakerBot {
811
839
  await this.store.save(state);
812
840
  return this.snapshot("pending", "retrying expired successor");
813
841
  }
814
- const prior = this.maker.quote.get(new PublicKey(replacement.prior));
842
+ const prior = this.maker.quote.get(new PublicKey2(replacement.prior));
815
843
  const previous = await prior.status();
816
844
  if (previous.status === "active" && ["validated", "reserving"].includes(snapshot.status)) {
817
845
  const price = BigInt(market.oraclePriceE8.toString());
@@ -869,16 +897,16 @@ class MakerBot {
869
897
  state.quote,
870
898
  state.replacement?.prior,
871
899
  this.requestId()?.toBase58(),
872
- state.replacement ? this.maker.quote.replacement(new PublicKey(state.replacement.prior), state.replacement.nonce).nextId.toBase58() : undefined
900
+ state.replacement ? this.maker.quote.replacement(new PublicKey2(state.replacement.prior), state.replacement.nonce).nextId.toBase58() : undefined
873
901
  ].filter((id) => Boolean(id))
874
902
  });
875
903
  this.lastCleanup = Date.now();
876
904
  }
877
- let [market, lamports] = await Promise.all([
905
+ let [market, lamports, now] = await Promise.all([
878
906
  this.maker.read("Market", this.maker.marketAddress),
879
- this.maker.options.connection.getBalance(this.maker.options.wallet.publicKey, "confirmed")
907
+ this.maker.options.connection.getBalance(this.maker.options.wallet.publicKey, "confirmed"),
908
+ chainTime(this.maker.options.connection)
880
909
  ]);
881
- let now = Math.floor(Date.now() / 1000);
882
910
  const reason = this.stopping ? "shutdown" : quoteHealth(market, now) ?? (BigInt(lamports) < (this.policy.gasReserveLamports ?? 50000000n) ? "operator SOL reserve" : undefined);
883
911
  if (reason) {
884
912
  const stopped = await this.cancel();
@@ -896,7 +924,7 @@ class MakerBot {
896
924
  return this.snapshot("pending", hedgeReason);
897
925
  if (this.hedge) {
898
926
  market = await this.maker.read("Market", this.maker.marketAddress);
899
- now = Math.floor(Date.now() / 1000);
927
+ now = await chainTime(this.maker.options.connection);
900
928
  const changed = quoteHealth(market, now);
901
929
  if (changed) {
902
930
  const stopped = await this.cancel();
@@ -910,7 +938,7 @@ class MakerBot {
910
938
  await this.store.save(state);
911
939
  }
912
940
  if (state.quote) {
913
- const handle2 = this.maker.quote.get(new PublicKey(state.quote));
941
+ const handle2 = this.maker.quote.get(new PublicKey2(state.quote));
914
942
  const quote = await handle2.status();
915
943
  if (terminal.has(quote.status)) {
916
944
  state.quote = undefined;
@@ -1010,7 +1038,7 @@ class MakerBot {
1010
1038
  const state = this.state;
1011
1039
  if (this.hedge && (state.hedge?.pending || state.inventoryPendingSince !== undefined && Date.now() - state.inventoryPendingSince >= this.hedge.policy.maxUnhedgedMs))
1012
1040
  return;
1013
- const handle = this.maker.quote.get(new PublicKey(state.quote));
1041
+ const handle = this.maker.quote.get(new PublicKey2(state.quote));
1014
1042
  const quote = await this.maker.quoteState(handle.id);
1015
1043
  if (!("active" in quote.status) || quote.expiry.toNumber() <= now)
1016
1044
  return;
@@ -1022,7 +1050,24 @@ class MakerBot {
1022
1050
  const drift = price > reference ? price - reference : reference - price;
1023
1051
  if (!quote.marketRevision.eq(market.revision) || reference <= 0n || drift * 10000n > reference * BigInt(Math.min(Math.floor(market.config.maxQuoteDeviationBps / 4), 25)))
1024
1052
  return;
1025
- const renewed = await handle.renew({ validForSeconds: duration });
1053
+ let renewed;
1054
+ try {
1055
+ renewed = await handle.renew({ validForSeconds: duration });
1056
+ } catch (error) {
1057
+ const code = error.error?.errorCode?.code;
1058
+ if (!["InvalidQuote", "WrongQuoteStatus"].includes(code ?? ""))
1059
+ throw error;
1060
+ const [latest, current] = await Promise.all([
1061
+ this.maker.read("Market", this.maker.marketAddress),
1062
+ this.maker.quoteState(handle.id)
1063
+ ]);
1064
+ const currentPrice = BigInt(latest.oraclePriceE8.toString());
1065
+ const quotedPrice = BigInt(current.oracleReferenceE8.toString());
1066
+ const moved = currentPrice > quotedPrice ? currentPrice - quotedPrice : quotedPrice - currentPrice;
1067
+ if (!("active" in current.status) || current.expiry.toNumber() <= Math.floor(Date.now() / 1000) || !current.marketRevision.eq(latest.revision) || quoteHealth(latest, Math.floor(Date.now() / 1000)) || moved * 10000n > quotedPrice * BigInt(Math.min(Math.floor(latest.config.maxQuoteDeviationBps / 4), 25)))
1068
+ return;
1069
+ throw error;
1070
+ }
1026
1071
  if (state.request)
1027
1072
  state.request.expiresAt = renewed.expiresAt;
1028
1073
  await this.store.save(state);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@depthhq/maker-sdk",
3
- "version": "0.1.16",
3
+ "version": "0.1.18",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },