@depthhq/sdk 0.1.2 → 0.1.3

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.
@@ -7,11 +7,12 @@ export declare class FundingClient {
7
7
  readonly connection: Connection;
8
8
  readonly owner: PublicKey;
9
9
  private readonly queue;
10
+ readonly settlement: "keeper" | "wallet";
10
11
  readonly market: PublicKey;
11
12
  readonly protocol: PublicKey;
12
13
  readonly balance: PublicKey;
13
14
  readonly marketAccount: PublicKey;
14
- constructor(program: anchor.Program, connection: Connection, owner: PublicKey, queue: Queue);
15
+ constructor(program: anchor.Program, connection: Connection, owner: PublicKey, queue: Queue, settlement?: "keeper" | "wallet");
15
16
  private assertOwner;
16
17
  read<T>(name: string, address: PublicKey): Promise<T | undefined>;
17
18
  private recoveryKey;
@@ -76,15 +76,17 @@ class FundingClient {
76
76
  connection;
77
77
  owner;
78
78
  queue;
79
+ settlement;
79
80
  market;
80
81
  protocol;
81
82
  balance;
82
83
  marketAccount;
83
- constructor(program, connection, owner, queue) {
84
+ constructor(program, connection, owner, queue, settlement = "wallet") {
84
85
  this.program = program;
85
86
  this.connection = connection;
86
87
  this.owner = owner;
87
88
  this.queue = queue;
89
+ this.settlement = settlement;
88
90
  this.market = marketAddress(program.programId);
89
91
  this.protocol = protocolAddress(program.programId);
90
92
  this.balance = balanceAddress(owner, program.programId);
@@ -418,7 +420,7 @@ class TransferHandle {
418
420
  return true;
419
421
  if (transfer.state === "rejected" || transfer.state === "refunded" || transfer.state === "cancelled")
420
422
  throw new DepthError("MARGIN_REJECTED", "confirmation", `Margin transfer ${transfer.state}`, false, "Read the receipt and private account before retrying", this.id.toBase58());
421
- if (transfer.state === "escrow") {
423
+ if (transfer.state === "escrow" && this.client.settlement === "wallet") {
422
424
  try {
423
425
  await this.settle();
424
426
  } catch (error) {
@@ -1,6 +1,9 @@
1
1
  import * as anchor from "@anchor-lang/core";
2
- import { Transaction } from "@solana/web3.js";
2
+ import { PublicKey, Transaction, TransactionInstruction } from "@solana/web3.js";
3
3
  export declare function applyDepthComputeBudget(transaction: Transaction): void;
4
4
  export declare class DepthProvider extends anchor.AnchorProvider {
5
+ private readonly cleanup;
6
+ deferClose(id: PublicKey, instruction: TransactionInstruction): void;
7
+ appendCleanup(transaction: Transaction): Promise<string[]>;
5
8
  sendAndConfirm(transaction: Parameters<anchor.AnchorProvider["sendAndConfirm"]>[0], signers?: Parameters<anchor.AnchorProvider["sendAndConfirm"]>[1], options?: Parameters<anchor.AnchorProvider["sendAndConfirm"]>[2]): Promise<string>;
6
9
  }
@@ -1,6 +1,7 @@
1
1
  import * as anchor from "@anchor-lang/core";
2
2
  import { SOL_USD_MARKET_ID } from "@depthhq/bindings";
3
3
  import { PublicKey } from "@solana/web3.js";
4
+ import { DepthProvider } from "../chain/provider.js";
4
5
  import { AppAdmin } from "../app/app.js";
5
6
  import { MarketCatalog } from "../catalog/catalog.js";
6
7
  import { FundingDiscovery } from "../account/discovery.js";
@@ -16,7 +17,9 @@ import type { CloseInput, DepthOptions, ResolvedDepthOptions, MarketState, Reduc
16
17
  export declare class DepthClient {
17
18
  readonly options: ResolvedDepthOptions;
18
19
  readonly program: anchor.Program;
19
- readonly provider: anchor.AnchorProvider;
20
+ readonly provider: DepthProvider;
21
+ private readonly views;
22
+ private readonly viewReads;
20
23
  readonly position: ReturnType<DepthClient["positionActions"]>;
21
24
  readonly marketAddress: anchor.web3.PublicKey;
22
25
  readonly protocolAddress: anchor.web3.PublicKey;
@@ -133,7 +136,12 @@ export declare class DepthClient {
133
136
  }>;
134
137
  };
135
138
  private orderAddress;
139
+ private deferViewClose;
140
+ private viewState;
141
+ private summaryView;
142
+ private readSummaryView;
136
143
  private submitTrade;
144
+ private waitForMarket;
137
145
  private validateTrade;
138
146
  }
139
147
  export declare function createDepth(options: DepthOptions): Promise<DepthClient>;
@@ -11,10 +11,14 @@ export type DepthWallet = {
11
11
  export type DepthOptions = ConnectionOptions & {
12
12
  appId?: PublicKey | string;
13
13
  wallet: DepthWallet;
14
+ privateViews?: "memory" | "device";
15
+ settlement?: "keeper" | "wallet";
14
16
  };
15
17
  export type ResolvedDepthOptions = ResolvedConnection & {
16
18
  appId?: PublicKey;
17
19
  wallet: DepthWallet;
20
+ privateViews?: "memory" | "device";
21
+ settlement?: "keeper" | "wallet";
18
22
  };
19
23
  export type TradeTerms = {
20
24
  marketId: typeof SOL_USD_MARKET_ID;
package/dist/index.js CHANGED
@@ -43628,7 +43628,7 @@ import {
43628
43628
  recoveryConfigAddress as recoveryConfigAddress2,
43629
43629
  SOL_USD_MARKET_ID as SOL_USD_MARKET_ID3
43630
43630
  } from "@depthhq/bindings";
43631
- import { PublicKey as PublicKey9 } from "@solana/web3.js";
43631
+ import { PublicKey as PublicKey10 } from "@solana/web3.js";
43632
43632
 
43633
43633
  // src/chain/random.ts
43634
43634
  function randomBytes3(length) {
@@ -44726,6 +44726,7 @@ import { DEPTH_IDL as DEPTH_IDL2, DEPTH_PROGRAM_ID as DEPTH_PROGRAM_ID3 } from "
44726
44726
  import {
44727
44727
  ComputeBudgetProgram,
44728
44728
  SendTransactionError,
44729
+ PublicKey as PublicKey4,
44729
44730
  Transaction
44730
44731
  } from "@solana/web3.js";
44731
44732
  var queuedInstructions = new Set([
@@ -44768,11 +44769,52 @@ function applyDepthComputeBudget(transaction) {
44768
44769
  }
44769
44770
 
44770
44771
  class DepthProvider extends anchor3.AnchorProvider {
44772
+ cleanup = new Map;
44773
+ deferClose(id, instruction) {
44774
+ this.cleanup.set(id.toBase58(), instruction);
44775
+ }
44776
+ async appendCleanup(transaction) {
44777
+ if (!this.cleanup.size || transaction.signatures.some((item) => item.signature))
44778
+ return [];
44779
+ const entries = [...this.cleanup.entries()].slice(0, 64);
44780
+ const queued = new Set(entries.map(([, instruction]) => instruction));
44781
+ transaction.instructions = transaction.instructions.filter((instruction) => !queued.has(instruction));
44782
+ const accounts2 = await this.connection.getMultipleAccountsInfo(entries.map(([id]) => new PublicKey4(id)), "confirmed");
44783
+ const added = [];
44784
+ for (let index = 0;index < entries.length; index++) {
44785
+ const [id, instruction] = entries[index];
44786
+ if (!accounts2[index]?.owner.equals(DEPTH_PROGRAM_ID3)) {
44787
+ this.cleanup.delete(id);
44788
+ continue;
44789
+ }
44790
+ const candidate = new Transaction({
44791
+ feePayer: transaction.feePayer ?? this.wallet.publicKey,
44792
+ recentBlockhash: transaction.recentBlockhash ?? PublicKey4.default.toBase58()
44793
+ }).add(...transaction.instructions, instruction);
44794
+ try {
44795
+ candidate.serialize({
44796
+ requireAllSignatures: false,
44797
+ verifySignatures: false
44798
+ });
44799
+ } catch {
44800
+ continue;
44801
+ }
44802
+ transaction.add(instruction);
44803
+ added.push(id);
44804
+ }
44805
+ return added;
44806
+ }
44771
44807
  async sendAndConfirm(transaction, signers, options) {
44772
- if (transaction instanceof Transaction)
44808
+ let cleanup = [];
44809
+ if (transaction instanceof Transaction) {
44773
44810
  applyDepthComputeBudget(transaction);
44811
+ cleanup = await this.appendCleanup(transaction);
44812
+ }
44774
44813
  try {
44775
- return await super.sendAndConfirm(transaction, signers, options);
44814
+ const signature = await super.sendAndConfirm(transaction, signers, options);
44815
+ for (const id of cleanup)
44816
+ this.cleanup.delete(id);
44817
+ return signature;
44776
44818
  } catch (error) {
44777
44819
  if (!(error instanceof Error) || !error.message.includes("Unknown action 'undefined'"))
44778
44820
  throw error;
@@ -45023,7 +45065,7 @@ import {
45023
45065
  transferAddress,
45024
45066
  withdrawalAddress
45025
45067
  } from "@depthhq/bindings";
45026
- import { PublicKey as PublicKey4 } from "@solana/web3.js";
45068
+ import { PublicKey as PublicKey5 } from "@solana/web3.js";
45027
45069
 
45028
45070
  // src/chain/transaction.ts
45029
45071
  init_buffer();
@@ -45181,7 +45223,7 @@ class FundingDiscovery {
45181
45223
  const limit = options.limit ?? 20;
45182
45224
  if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100)
45183
45225
  throw new RangeError("limit must be an integer from 1 to 100");
45184
- const cursor = options.cursor ? new PublicKey4(options.cursor).toBase58() : undefined;
45226
+ const cursor = options.cursor ? new PublicKey5(options.cursor).toBase58() : undefined;
45185
45227
  const accounts2 = await this.connection.getProgramAccounts(this.program.programId, {
45186
45228
  commitment: "confirmed",
45187
45229
  dataSlice: { offset: 0, length: 0 },
@@ -45213,7 +45255,7 @@ class FundingDiscovery {
45213
45255
  const current = status(state);
45214
45256
  if (!state.toMarket || current !== "settled")
45215
45257
  return;
45216
- const orderId = PublicKey4.findProgramAddressSync([
45258
+ const orderId = PublicKey5.findProgramAddressSync([
45217
45259
  Buffer2.from("order"),
45218
45260
  state.market.toBuffer(),
45219
45261
  this.owner.toBuffer(),
@@ -45260,7 +45302,7 @@ class FundingDiscovery {
45260
45302
  const limit = options.limit ?? 20;
45261
45303
  if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100)
45262
45304
  throw new RangeError("limit must be an integer from 1 to 100");
45263
- const cursor = options.cursor ? new PublicKey4(options.cursor).toBase58() : undefined;
45305
+ const cursor = options.cursor ? new PublicKey5(options.cursor).toBase58() : undefined;
45264
45306
  const types3 = [
45265
45307
  {
45266
45308
  name: "depositReceipt",
@@ -45351,7 +45393,7 @@ class FundingDiscovery {
45351
45393
  throw new Error("Order changed during discovery");
45352
45394
  const bytes = Buffer2.alloc(8);
45353
45395
  bytes.writeBigUInt64LE(nonce);
45354
- canonical = PublicKey4.findProgramAddressSync([
45396
+ canonical = PublicKey5.findProgramAddressSync([
45355
45397
  Buffer2.from("order"),
45356
45398
  state.market.toBuffer(),
45357
45399
  this.owner.toBuffer(),
@@ -45437,15 +45479,17 @@ class FundingClient {
45437
45479
  connection;
45438
45480
  owner;
45439
45481
  queue;
45482
+ settlement;
45440
45483
  market;
45441
45484
  protocol;
45442
45485
  balance;
45443
45486
  marketAccount;
45444
- constructor(program, connection, owner, queue) {
45487
+ constructor(program, connection, owner, queue, settlement = "wallet") {
45445
45488
  this.program = program;
45446
45489
  this.connection = connection;
45447
45490
  this.owner = owner;
45448
45491
  this.queue = queue;
45492
+ this.settlement = settlement;
45449
45493
  this.market = marketAddress3(program.programId);
45450
45494
  this.protocol = protocolAddress2(program.programId);
45451
45495
  this.balance = balanceAddress(owner, program.programId);
@@ -45779,7 +45823,7 @@ class TransferHandle {
45779
45823
  return true;
45780
45824
  if (transfer.state === "rejected" || transfer.state === "refunded" || transfer.state === "cancelled")
45781
45825
  throw new DepthError("MARGIN_REJECTED", "confirmation", `Margin transfer ${transfer.state}`, false, "Read the receipt and private account before retrying", this.id.toBase58());
45782
- if (transfer.state === "escrow") {
45826
+ if (transfer.state === "escrow" && this.client.settlement === "wallet") {
45783
45827
  try {
45784
45828
  await this.settle();
45785
45829
  } catch (error) {
@@ -45883,15 +45927,15 @@ async function balanceState(program, connection, owner, address2) {
45883
45927
  // src/account/token.ts
45884
45928
  init_buffer();
45885
45929
  import {
45886
- PublicKey as PublicKey5,
45930
+ PublicKey as PublicKey6,
45887
45931
  SystemProgram,
45888
45932
  Transaction as Transaction2,
45889
- TransactionInstruction
45933
+ TransactionInstruction as TransactionInstruction2
45890
45934
  } from "@solana/web3.js";
45891
- var tokenProgram = new PublicKey5("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
45892
- var associatedProgram = new PublicKey5("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL");
45935
+ var tokenProgram = new PublicKey6("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
45936
+ var associatedProgram = new PublicKey6("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL");
45893
45937
  function associatedTokenAddress(owner, mint) {
45894
- return PublicKey5.findProgramAddressSync([owner.toBuffer(), tokenProgram.toBuffer(), mint.toBuffer()], associatedProgram)[0];
45938
+ return PublicKey6.findProgramAddressSync([owner.toBuffer(), tokenProgram.toBuffer(), mint.toBuffer()], associatedProgram)[0];
45895
45939
  }
45896
45940
  function tokenBalance(info, owner, mint) {
45897
45941
  if (!info.owner.equals(tokenProgram) || info.data.length < 165 || !info.data.subarray(0, 32).equals(mint.toBuffer()) || !info.data.subarray(32, 64).equals(owner.toBuffer()) || info.data[108] !== 1)
@@ -45920,7 +45964,7 @@ async function destinationTokenAccount(provider, owner, mint) {
45920
45964
  const address2 = associatedTokenAddress(owner, mint);
45921
45965
  let info = await provider.connection.getAccountInfo(address2, "confirmed");
45922
45966
  if (!info) {
45923
- const instruction = new TransactionInstruction({
45967
+ const instruction = new TransactionInstruction2({
45924
45968
  programId: associatedProgram,
45925
45969
  data: Buffer2.from([1]),
45926
45970
  keys: [
@@ -45962,7 +46006,7 @@ import {
45962
46006
  marketAddress as marketAddress4,
45963
46007
  SOL_USD_MARKET_ID as SOL_USD_MARKET_ID2
45964
46008
  } from "@depthhq/bindings";
45965
- import { PublicKey as PublicKey6 } from "@solana/web3.js";
46009
+ import { PublicKey as PublicKey7 } from "@solana/web3.js";
45966
46010
 
45967
46011
  // src/position/pnl.ts
45968
46012
  var scale = 100000000000n;
@@ -46192,11 +46236,11 @@ class DepthOperation {
46192
46236
  throw new DepthError(result.status === "pending" ? "FILL_VIEW_PENDING" : "FILL_VIEW_STALE", "confirmation", result.status === "pending" ? "Private fill computation is still pending" : "Private fill view is stale", result.status === "pending", result.status === "pending" ? "Call privateResult again with the same operation" : "Restore this operation and request a fresh fill view", this.id.toBase58());
46193
46237
  this.fill = {
46194
46238
  ...result,
46195
- positionId: marketAccountAddress2(new PublicKey6(snapshot.owner), this.program.programId)
46239
+ positionId: marketAccountAddress2(new PublicKey7(snapshot.owner), this.program.programId)
46196
46240
  };
46197
46241
  }
46198
46242
  try {
46199
- await this.view.close();
46243
+ await this.view.deferClose();
46200
46244
  } catch (error) {
46201
46245
  let remaining;
46202
46246
  try {
@@ -46212,7 +46256,7 @@ class DepthOperation {
46212
46256
  return this.fill;
46213
46257
  }
46214
46258
  cleanupError(cause) {
46215
- return new DepthError("FILL_VIEW_CLEANUP_PENDING", "confirmation", "Private fill was read but its temporary view is still open", true, "Call privateResult again with the same operation to close the view", this.id.toBase58(), { cause });
46259
+ return new DepthError("FILL_VIEW_CLEANUP_PENDING", "confirmation", "Private fill was read but receipt cleanup could not be scheduled", true, "Call privateResult again with the same operation to schedule cleanup", this.id.toBase58(), { cause });
46216
46260
  }
46217
46261
  }
46218
46262
 
@@ -46223,22 +46267,22 @@ import {
46223
46267
  marketAddress as marketAddress5,
46224
46268
  protocolAddress as protocolAddress3
46225
46269
  } from "@depthhq/bindings";
46226
- import { PublicKey as PublicKey7 } from "@solana/web3.js";
46270
+ import { PublicKey as PublicKey8 } from "@solana/web3.js";
46227
46271
 
46228
46272
  class DepthRecovery {
46229
46273
  program;
46230
46274
  connection;
46231
46275
  owner;
46232
46276
  market = marketAddress5();
46233
- winddown = PublicKey7.findProgramAddressSync([Buffer2.from("winddown"), this.market.toBuffer()], DEPTH_PROGRAM_ID5)[0];
46277
+ winddown = PublicKey8.findProgramAddressSync([Buffer2.from("winddown"), this.market.toBuffer()], DEPTH_PROGRAM_ID5)[0];
46234
46278
  marketAccount;
46235
46279
  claim;
46236
46280
  constructor(program, connection, owner) {
46237
46281
  this.program = program;
46238
46282
  this.connection = connection;
46239
46283
  this.owner = owner;
46240
- this.marketAccount = PublicKey7.findProgramAddressSync([Buffer2.from("market-account"), this.market.toBuffer(), owner.toBuffer()], DEPTH_PROGRAM_ID5)[0];
46241
- this.claim = PublicKey7.findProgramAddressSync([Buffer2.from("winddown-claim"), this.marketAccount.toBuffer()], DEPTH_PROGRAM_ID5)[0];
46284
+ this.marketAccount = PublicKey8.findProgramAddressSync([Buffer2.from("market-account"), this.market.toBuffer(), owner.toBuffer()], DEPTH_PROGRAM_ID5)[0];
46285
+ this.claim = PublicKey8.findProgramAddressSync([Buffer2.from("winddown-claim"), this.marketAccount.toBuffer()], DEPTH_PROGRAM_ID5)[0];
46242
46286
  }
46243
46287
  async read(address2, name2) {
46244
46288
  const result = await this.connection.getAccountInfoAndContext(address2, "confirmed");
@@ -46328,7 +46372,7 @@ import {
46328
46372
  marketAddress as marketAddress6,
46329
46373
  transferAddress as transferAddress3
46330
46374
  } from "@depthhq/bindings";
46331
- import { PublicKey as PublicKey8 } from "@solana/web3.js";
46375
+ import { PublicKey as PublicKey9 } from "@solana/web3.js";
46332
46376
 
46333
46377
  // src/history/replay.ts
46334
46378
  function replayFill(state, fill, fundingIndex, maker = false) {
@@ -46407,7 +46451,7 @@ class ExecutionHistory {
46407
46451
  this.requestView = requestView;
46408
46452
  }
46409
46453
  orderAddress(trader, nonce) {
46410
- return PublicKey8.findProgramAddressSync([
46454
+ return PublicKey9.findProgramAddressSync([
46411
46455
  Buffer2.from("order"),
46412
46456
  marketAddress6(this.program.programId).toBuffer(),
46413
46457
  trader.toBuffer(),
@@ -46429,7 +46473,7 @@ class ExecutionHistory {
46429
46473
  }
46430
46474
  const result = this.fills.get(key) ?? await view.wait({ timeoutMs: 180000 });
46431
46475
  if (result.status === "stale") {
46432
- await view.close();
46476
+ await view.deferClose();
46433
46477
  this.views.delete(key);
46434
46478
  throw new Error("Historical private execution changed; retry with the same wallet");
46435
46479
  }
@@ -46437,7 +46481,7 @@ class ExecutionHistory {
46437
46481
  throw new Error("Historical private execution is still pending; retry with the same wallet");
46438
46482
  this.fills.set(key, result);
46439
46483
  try {
46440
- await view.close();
46484
+ await view.deferClose();
46441
46485
  } catch (error) {
46442
46486
  if (await this.connection.getAccountInfo(view.id, "confirmed"))
46443
46487
  throw error;
@@ -46805,19 +46849,151 @@ class DepthView {
46805
46849
  await new Promise((resolve) => setTimeout(resolve, Math.min(pollMs, deadline - Date.now())));
46806
46850
  }
46807
46851
  }
46852
+ async deferClose() {
46853
+ const provider = this.program.provider;
46854
+ if (typeof provider.deferClose !== "function") {
46855
+ await this.close();
46856
+ return;
46857
+ }
46858
+ const snapshot = await this.status();
46859
+ if (snapshot.status === "pending")
46860
+ throw new Error("Private view computation is still pending");
46861
+ const instruction = await this.closeBuilder().instruction();
46862
+ provider.deferClose(this.id, instruction);
46863
+ }
46864
+ closeBuilder() {
46865
+ const builder = this.kind === "balance" ? this.program.methods.closeBalanceView() : this.kind === "market" ? this.program.methods.closeMarketView() : this.kind === "fill" ? this.program.methods.closeFillView() : this.program.methods.closeQuoteView();
46866
+ return builder.accountsPartial({ owner: this.owner, view: this.id });
46867
+ }
46808
46868
  async close() {
46809
46869
  const snapshot = await this.status();
46810
46870
  if (snapshot.status === "pending") {
46811
46871
  throw new Error("Private view computation is still pending");
46812
46872
  }
46813
- const builder = this.kind === "balance" ? this.program.methods.closeBalanceView() : this.kind === "market" ? this.program.methods.closeMarketView() : this.kind === "fill" ? this.program.methods.closeFillView() : this.program.methods.closeQuoteView();
46814
- return builder.accountsPartial({ owner: this.owner, view: this.id }).rpc({ commitment: "confirmed" });
46873
+ return this.closeBuilder().rpc({ commitment: "confirmed" });
46874
+ }
46875
+ }
46876
+
46877
+ // src/view/cache.ts
46878
+ class DeviceViews {
46879
+ database;
46880
+ open() {
46881
+ return this.database ??= new Promise((resolve, reject) => {
46882
+ const request = indexedDB.open("depth-private-views", 1);
46883
+ request.onupgradeneeded = () => {
46884
+ request.result.createObjectStore("keys");
46885
+ request.result.createObjectStore("views");
46886
+ };
46887
+ request.onerror = () => reject(request.error);
46888
+ request.onblocked = () => reject(new Error("Private view storage is blocked"));
46889
+ request.onsuccess = () => resolve(request.result);
46890
+ });
46891
+ }
46892
+ async key(scope, proposed) {
46893
+ const database = await this.open();
46894
+ return new Promise((resolve, reject) => {
46895
+ const transaction = database.transaction("keys", "readwrite");
46896
+ const store = transaction.objectStore("keys");
46897
+ const request = store.get(scope);
46898
+ let value2;
46899
+ request.onsuccess = () => {
46900
+ value2 = request.result ?? proposed;
46901
+ if (!request.result)
46902
+ store.put(value2, scope);
46903
+ };
46904
+ transaction.oncomplete = () => resolve(value2);
46905
+ transaction.onabort = () => reject(transaction.error);
46906
+ transaction.onerror = () => reject(transaction.error);
46907
+ });
46908
+ }
46909
+ async read(id) {
46910
+ const database = await this.open();
46911
+ return new Promise((resolve, reject) => {
46912
+ const transaction = database.transaction("views", "readonly");
46913
+ const request = transaction.objectStore("views").get(id);
46914
+ transaction.oncomplete = () => resolve(request.result);
46915
+ transaction.onabort = () => reject(transaction.error);
46916
+ transaction.onerror = () => reject(transaction.error);
46917
+ });
46918
+ }
46919
+ async write(id, value2) {
46920
+ const database = await this.open();
46921
+ return new Promise((resolve, reject) => {
46922
+ const transaction = database.transaction("views", "readwrite");
46923
+ transaction.objectStore("views").put(value2, id);
46924
+ transaction.oncomplete = () => resolve();
46925
+ transaction.onabort = () => reject(transaction.error);
46926
+ transaction.onerror = () => reject(transaction.error);
46927
+ });
46928
+ }
46929
+ }
46930
+
46931
+ class ViewCache {
46932
+ scope;
46933
+ storage;
46934
+ memory = new Map;
46935
+ key;
46936
+ constructor(scope, storage) {
46937
+ this.scope = scope;
46938
+ this.storage = storage;
46939
+ }
46940
+ encryptionKey() {
46941
+ return this.key ??= crypto.subtle.generateKey({ name: "AES-GCM", length: 256 }, false, [
46942
+ "encrypt",
46943
+ "decrypt"
46944
+ ]).then((key) => this.storage.key(this.scope, key));
46945
+ }
46946
+ id(kind, account) {
46947
+ return `${this.scope}:${kind}:${account}`;
46948
+ }
46949
+ async get(kind, account, version) {
46950
+ const id = this.id(kind, account);
46951
+ let snapshot = this.memory.get(id);
46952
+ if (!snapshot && this.storage) {
46953
+ try {
46954
+ const envelope = await this.storage.read(id);
46955
+ if (envelope) {
46956
+ const bytes = await crypto.subtle.decrypt({
46957
+ name: "AES-GCM",
46958
+ iv: envelope.iv,
46959
+ additionalData: new TextEncoder().encode(id)
46960
+ }, await this.encryptionKey(), envelope.data);
46961
+ snapshot = JSON.parse(new TextDecoder().decode(bytes), (_, value2) => value2 && typeof value2 === "object" && typeof value2.$bigint === "string" ? BigInt(value2.$bigint) : value2);
46962
+ }
46963
+ } catch {
46964
+ return;
46965
+ }
46966
+ }
46967
+ if (!snapshot || snapshot.status !== "ready" || snapshot.version !== version || typeof snapshot.id !== "string" || !Number.isSafeInteger(snapshot.slot) || (kind === "balance" ? typeof snapshot.amount !== "bigint" : [
46968
+ "collateral",
46969
+ "reserved",
46970
+ "position",
46971
+ "entryPriceE8",
46972
+ "fundingCheckpoint"
46973
+ ].some((field) => typeof snapshot[field] !== "bigint")))
46974
+ return;
46975
+ this.memory.set(id, snapshot);
46976
+ return { ...snapshot };
46977
+ }
46978
+ async set(kind, account, snapshot) {
46979
+ if (snapshot.status !== "ready")
46980
+ return;
46981
+ const id = this.id(kind, account);
46982
+ this.memory.set(id, { ...snapshot });
46983
+ if (!this.storage)
46984
+ return;
46985
+ try {
46986
+ const iv = crypto.getRandomValues(new Uint8Array(12));
46987
+ const bytes = new TextEncoder().encode(JSON.stringify(snapshot, (_, value2) => typeof value2 === "bigint" ? { $bigint: value2.toString() } : value2));
46988
+ const data = await crypto.subtle.encrypt({ name: "AES-GCM", iv, additionalData: new TextEncoder().encode(id) }, await this.encryptionKey(), bytes);
46989
+ await this.storage.write(id, { iv, data });
46990
+ } catch {}
46815
46991
  }
46816
46992
  }
46817
46993
 
46818
46994
  // src/client/client.ts
46819
46995
  function pda(seeds, programId = DEPTH_PROGRAM_ID7) {
46820
- return PublicKey9.findProgramAddressSync(seeds, programId)[0];
46996
+ return PublicKey10.findProgramAddressSync(seeds, programId)[0];
46821
46997
  }
46822
46998
  function nonce() {
46823
46999
  return new anchor8.BN(randomBytes3(8), "le");
@@ -46830,6 +47006,8 @@ class DepthClient {
46830
47006
  options;
46831
47007
  program;
46832
47008
  provider;
47009
+ views;
47010
+ viewReads = new Map;
46833
47011
  position;
46834
47012
  marketAddress = marketAddress7();
46835
47013
  protocolAddress = protocolAddress4();
@@ -46838,7 +47016,7 @@ class DepthClient {
46838
47016
  return new MarketCatalog(this);
46839
47017
  }
46840
47018
  get funding() {
46841
- return new FundingClient(this.program, this.options.connection, this.options.wallet.publicKey, (name2, offset2) => this.queue(name2, offset2));
47019
+ return new FundingClient(this.program, this.options.connection, this.options.wallet.publicKey, (name2, offset2) => this.queue(name2, offset2), this.options.settlement ?? (this.options.environment && this.options.environment !== "localnet" ? "keeper" : "wallet"));
46842
47020
  }
46843
47021
  get app() {
46844
47022
  return new AppAdmin(this.program, this.options.connection, this.options.wallet.publicKey, this.funding);
@@ -46852,6 +47030,7 @@ class DepthClient {
46852
47030
  }
46853
47031
  constructor(options) {
46854
47032
  this.options = options;
47033
+ this.views = new ViewCache(`${"manifest" in options.deployment ? options.deployment.manifest.payload.genesisHash : "localnet"}:${DEPTH_PROGRAM_ID7.toBase58()}:${options.wallet.publicKey.toBase58()}`, options.privateViews === "device" && "manifest" in options.deployment && typeof indexedDB !== "undefined" && globalThis.crypto?.subtle ? new DeviceViews : undefined);
46855
47034
  this.provider = new DepthProvider(options.connection, options.wallet, {
46856
47035
  commitment: "confirmed",
46857
47036
  preflightCommitment: "confirmed"
@@ -46860,7 +47039,7 @@ class DepthClient {
46860
47039
  this.position = this.positionActions();
46861
47040
  }
46862
47041
  static async create(options) {
46863
- const appId = typeof options.appId === "string" ? new PublicKey9(options.appId) : options.appId;
47042
+ const appId = typeof options.appId === "string" ? new PublicKey10(options.appId) : options.appId;
46864
47043
  const resolved = {
46865
47044
  ...options,
46866
47045
  ...await resolveConnection(options),
@@ -47078,7 +47257,7 @@ class DepthClient {
47078
47257
  return this.funding.release(amount, clientNonce ?? BigInt(nonce().toString()));
47079
47258
  }
47080
47259
  async depositRequest(sourceOrAmount, amount, clientNonce) {
47081
- if (sourceOrAmount instanceof PublicKey9) {
47260
+ if (sourceOrAmount instanceof PublicKey10) {
47082
47261
  if (amount === undefined || clientNonce === undefined)
47083
47262
  throw new RangeError("Source deposits require amount and client nonce");
47084
47263
  return this.funding.deposit(sourceOrAmount, amount, clientNonce);
@@ -47089,7 +47268,7 @@ class DepthClient {
47089
47268
  return this.funding.deposit(source, sourceOrAmount, amount ?? BigInt(nonce().toString()));
47090
47269
  }
47091
47270
  async withdrawRequest(destinationOrAmount, amount, clientNonce) {
47092
- if (destinationOrAmount instanceof PublicKey9) {
47271
+ if (destinationOrAmount instanceof PublicKey10) {
47093
47272
  if (amount === undefined || clientNonce === undefined)
47094
47273
  throw new RangeError("Destination withdrawals require amount and client nonce");
47095
47274
  return this.funding.withdraw(destinationOrAmount, amount, clientNonce);
@@ -47124,11 +47303,7 @@ class DepthClient {
47124
47303
  const state = await actions.state();
47125
47304
  if (state.status === "uninitialized")
47126
47305
  return { ...state, status: "uninitialized", amount: 0n };
47127
- const view = await this.requestBalanceView();
47128
- const result = await view.wait(options);
47129
- if (result.status !== "pending")
47130
- await view.close();
47131
- return result;
47306
+ return this.summaryView("balance", state.id, options);
47132
47307
  }
47133
47308
  };
47134
47309
  return Object.assign(() => actions, actions);
@@ -47164,29 +47339,7 @@ class DepthClient {
47164
47339
  summary: async (options = {
47165
47340
  timeoutMs: 180000
47166
47341
  }) => {
47167
- const deadline = Date.now() + options.timeoutMs;
47168
- let view;
47169
- for (;; ) {
47170
- try {
47171
- view = await this.requestMarketView();
47172
- break;
47173
- } catch (error) {
47174
- const code2 = error.error?.errorCode?.code;
47175
- if (code2 !== "WrongMarketAccountStatus" && !String(error).includes("Market account is processing another operation")) {
47176
- throw error;
47177
- }
47178
- if (Date.now() >= deadline)
47179
- throw error;
47180
- await new Promise((resolve) => setTimeout(resolve, 1000));
47181
- }
47182
- }
47183
- const result = await view.wait({
47184
- ...options,
47185
- timeoutMs: Math.max(0, deadline - Date.now())
47186
- });
47187
- if (result.status !== "pending")
47188
- await view.close();
47189
- return result;
47342
+ return this.summaryView("market", address2.toBase58(), options);
47190
47343
  }
47191
47344
  };
47192
47345
  return Object.assign((positionId) => this.positionActions(positionId), actions);
@@ -47232,6 +47385,75 @@ class DepthClient {
47232
47385
  u64(clientNonce, "clientNonce").toArrayLike(Buffer2, "le", 8)
47233
47386
  ]);
47234
47387
  }
47388
+ async deferViewClose(kind, id) {
47389
+ const view = new PublicKey10(id);
47390
+ const method = kind === "balance" ? this.program.methods.closeBalanceView : this.program.methods.closeMarketView;
47391
+ this.provider.deferClose(view, await method().accountsPartial({
47392
+ owner: this.options.wallet.publicKey,
47393
+ view
47394
+ }).instruction());
47395
+ }
47396
+ async viewState(kind, id) {
47397
+ if (kind === "balance")
47398
+ return this.account().state();
47399
+ const state = await this.read("MarketAccount", new PublicKey10(id));
47400
+ if (!state.owner.equals(this.options.wallet.publicKey) || !state.market.equals(this.marketAddress))
47401
+ throw new Error("Position does not belong to this wallet and market");
47402
+ return {
47403
+ status: enumName2(state.status),
47404
+ version: BigInt(state.version.toString())
47405
+ };
47406
+ }
47407
+ summaryView(kind, id, options) {
47408
+ const key = `${kind}:${id}`;
47409
+ const pending = this.viewReads.get(key);
47410
+ if (pending)
47411
+ return pending;
47412
+ const request = this.readSummaryView(kind, id, options).finally(() => {
47413
+ if (this.viewReads.get(key) === request)
47414
+ this.viewReads.delete(key);
47415
+ });
47416
+ this.viewReads.set(key, request);
47417
+ return request;
47418
+ }
47419
+ async readSummaryView(kind, id, options) {
47420
+ if (!Number.isSafeInteger(options.timeoutMs) || options.timeoutMs < 0)
47421
+ throw new RangeError("timeoutMs must be a nonnegative integer");
47422
+ const deadline = Date.now() + options.timeoutMs;
47423
+ for (;; ) {
47424
+ const state = await this.viewState(kind, id);
47425
+ if (state.status === "ready") {
47426
+ const cached = await this.views.get(kind, id, state.version);
47427
+ if (cached) {
47428
+ await this.deferViewClose(kind, cached.id);
47429
+ return cached;
47430
+ }
47431
+ try {
47432
+ const view = kind === "balance" ? await this.requestBalanceView() : await this.requestMarketView();
47433
+ const result = await view.wait({
47434
+ ...options,
47435
+ timeoutMs: Math.max(0, deadline - Date.now())
47436
+ });
47437
+ if (result.status === "pending")
47438
+ return result;
47439
+ await this.deferViewClose(kind, result.id);
47440
+ const latest = await this.viewState(kind, id);
47441
+ if (result.status === "ready" && latest.status === "ready" && latest.version === result.version) {
47442
+ const snapshot = { ...result, readAt: Date.now() };
47443
+ await this.views.set(kind, id, snapshot);
47444
+ return snapshot;
47445
+ }
47446
+ } catch (error) {
47447
+ const code2 = error.error?.errorCode?.code;
47448
+ if (code2 !== "WrongMarketAccountStatus" && !String(error).includes("processing another operation"))
47449
+ throw error;
47450
+ }
47451
+ }
47452
+ if (Date.now() >= deadline)
47453
+ throw new Error("Private account is still processing an operation");
47454
+ await new Promise((resolve) => setTimeout(resolve, Math.min(options.pollMs ?? 1000, deadline - Date.now())));
47455
+ }
47456
+ }
47235
47457
  async submitTrade(input, intent) {
47236
47458
  const appId = this.options.appId;
47237
47459
  if (!appId)
@@ -47263,43 +47485,13 @@ class DepthClient {
47263
47485
  throw new Error("Position does not belong to this wallet and market");
47264
47486
  await this.ensureBalanceReady();
47265
47487
  await this.ensureMarketReady();
47266
- let [market, protocol, account, app] = await Promise.all([
47267
- this.market(),
47268
- this.read("Protocol", this.protocolAddress),
47269
- this.read("MarketAccount", this.accountAddress()),
47270
- this.read("AppConfig", appAddress2(appId))
47271
- ]);
47272
- const deadline = tradeDeadline(input, Math.floor(Date.now() / 1000), market.state.config.maxOrderQueueSeconds);
47488
+ let [market, protocol, account, app] = await this.waitForMarket(input, appId, intent);
47489
+ let deadline;
47273
47490
  tradeLimit(input, "long", BigInt(market.state.oraclePriceE8.toString()));
47274
- this.validateTrade(market.state, protocol, account, app, appId, intent, deadline);
47275
47491
  if (additionalMargin > 0n) {
47276
47492
  const transfer = await this.funding.allocate(additionalMargin, clientNonce);
47277
- const allocationDeadline = Date.now() + 120000;
47278
- for (;; ) {
47279
- const current = await transfer.status();
47280
- if (current.state === "settled")
47281
- break;
47282
- if (current.state === "rejected" || current.state === "refunded")
47283
- throw new Error(`Margin allocation ${transfer.id.toBase58()} ${current.state}`);
47284
- if (current.state === "escrow") {
47285
- try {
47286
- await transfer.settle();
47287
- } catch (error) {
47288
- if ((await transfer.status()).state === "escrow")
47289
- throw error;
47290
- }
47291
- }
47292
- if (Date.now() >= allocationDeadline)
47293
- throw new Error(`Margin allocation ${transfer.id.toBase58()} is pending`);
47294
- await new Promise((resolve) => setTimeout(resolve, 1000));
47295
- }
47296
- [market, protocol, account, app] = await Promise.all([
47297
- this.market(),
47298
- this.read("Protocol", this.protocolAddress),
47299
- this.read("MarketAccount", this.accountAddress()),
47300
- this.read("AppConfig", appAddress2(appId))
47301
- ]);
47302
- this.validateTrade(market.state, protocol, account, app, appId, intent, deadline);
47493
+ await transfer.confirm({ timeoutMs: 120000 });
47494
+ [market, protocol, account, app] = await this.waitForMarket(input, appId, intent);
47303
47495
  }
47304
47496
  const viewDeadline = Date.now() + 120000;
47305
47497
  let position;
@@ -47317,15 +47509,13 @@ class DepthClient {
47317
47509
  await new Promise((resolve) => setTimeout(resolve, 1000));
47318
47510
  continue;
47319
47511
  }
47320
- [market, account] = await Promise.all([
47321
- this.market(),
47322
- this.read("MarketAccount", this.accountAddress())
47323
- ]);
47512
+ [market, protocol, account, app] = await this.waitForMarket(input, appId, intent);
47324
47513
  if (position.status === "ready" && enumName2(account.status) === "ready" && account.version.toString() === position.version.toString()) {
47325
47514
  break;
47326
47515
  }
47327
47516
  await new Promise((resolve) => setTimeout(resolve, 1000));
47328
47517
  }
47518
+ deadline = tradeDeadline(input, Math.floor(Date.now() / 1000), market.state.config.maxOrderQueueSeconds);
47329
47519
  this.validateTrade(market.state, protocol, account, app, appId, intent, deadline);
47330
47520
  if (position.status !== "ready" || position.collateral === undefined || position.position === undefined)
47331
47521
  throw new Error(`Private position view ${position.id} is ${position.status}`);
@@ -47395,6 +47585,29 @@ class DepthClient {
47395
47585
  ]).rpc({ commitment: "confirmed" }).catch((error) => submissionError(order.toBase58(), error));
47396
47586
  return new DepthOperation(order, signature, this.program, this.options.connection, (id) => this.requestFillView(id), position);
47397
47587
  }
47588
+ async waitForMarket(input, appId, intent) {
47589
+ const until = Date.now() + 60000;
47590
+ for (;; ) {
47591
+ const [market, protocol, account, app] = await Promise.all([
47592
+ this.market(),
47593
+ this.read("Protocol", this.protocolAddress),
47594
+ this.read("MarketAccount", this.accountAddress()),
47595
+ this.read("AppConfig", appAddress2(appId))
47596
+ ]);
47597
+ const deadline = tradeDeadline(input, Math.floor(Date.now() / 1000), market.state.config.maxOrderQueueSeconds);
47598
+ try {
47599
+ this.validateTrade(market.state, protocol, account, app, appId, intent, deadline);
47600
+ return [market, protocol, account, app];
47601
+ } catch (error) {
47602
+ if (!(error instanceof Error) || ![
47603
+ "Waiting for maker liquidity",
47604
+ "Market is processing another order"
47605
+ ].includes(error.message) || Date.now() >= until)
47606
+ throw error;
47607
+ }
47608
+ await new Promise((resolve) => setTimeout(resolve, 1000));
47609
+ }
47610
+ }
47398
47611
  validateTrade(market, protocol, account, app, appId, intent = "increase", deadline) {
47399
47612
  const now = Math.floor(Date.now() / 1000);
47400
47613
  const marketStatus = enumName2(market.status);
@@ -47407,9 +47620,10 @@ class DepthClient {
47407
47620
  if (enumName2(account.status) !== "ready" || !account.market.equals(this.marketAddress) || !account.owner.equals(this.options.wallet.publicKey)) {
47408
47621
  throw new Error("Market account is unavailable");
47409
47622
  }
47410
- if (market.pendingOrders !== 0 || market.activeQuoteCount === 0) {
47411
- throw new Error("Market is processing another order or has no quotes");
47412
- }
47623
+ if (market.pendingOrders !== 0)
47624
+ throw new Error("Market is processing another order");
47625
+ if (market.activeQuoteCount === 0)
47626
+ throw new Error("Waiting for maker liquidity");
47413
47627
  const age = now - market.oracleTimestamp.toNumber();
47414
47628
  if (age < 0 || age > market.config.maxOracleAgeSeconds) {
47415
47629
  throw new Error("Oracle update is stale");
@@ -0,0 +1,31 @@
1
+ import type { BalanceSnapshot, PositionSnapshot } from "./view.js";
2
+ type Snapshot = BalanceSnapshot | PositionSnapshot;
3
+ type Kind = "balance" | "market";
4
+ type Envelope = {
5
+ iv: Uint8Array;
6
+ data: ArrayBuffer;
7
+ };
8
+ export interface ViewStorage {
9
+ key(scope: string, proposed: CryptoKey): Promise<CryptoKey>;
10
+ read(id: string): Promise<Envelope | undefined>;
11
+ write(id: string, value: Envelope): Promise<void>;
12
+ }
13
+ export declare class DeviceViews implements ViewStorage {
14
+ private database?;
15
+ private open;
16
+ key(scope: string, proposed: CryptoKey): Promise<CryptoKey>;
17
+ read(id: string): Promise<Envelope | undefined>;
18
+ write(id: string, value: Envelope): Promise<void>;
19
+ }
20
+ export declare class ViewCache {
21
+ private readonly scope;
22
+ private readonly storage?;
23
+ private readonly memory;
24
+ private key?;
25
+ constructor(scope: string, storage?: ViewStorage | undefined);
26
+ private encryptionKey;
27
+ private id;
28
+ get<T extends Snapshot>(kind: Kind, account: string, version: bigint): Promise<T | undefined>;
29
+ set(kind: Kind, account: string, snapshot: Snapshot): Promise<void>;
30
+ }
31
+ export {};
@@ -4,6 +4,7 @@ export type BalanceSnapshot = {
4
4
  status: "uninitialized" | "pending" | "ready" | "stale";
5
5
  id: string;
6
6
  version: bigint;
7
+ readAt?: number;
7
8
  slot: number;
8
9
  amount?: bigint;
9
10
  };
@@ -11,6 +12,7 @@ export type PositionSnapshot = {
11
12
  status: "pending" | "ready" | "stale";
12
13
  id: string;
13
14
  version: bigint;
15
+ readAt?: number;
14
16
  slot: number;
15
17
  collateral?: bigint;
16
18
  reserved?: bigint;
@@ -55,5 +57,7 @@ export declare class DepthView {
55
57
  timeoutMs: number;
56
58
  pollMs?: number;
57
59
  }): Promise<BalanceSnapshot | PositionSnapshot | FillSnapshot | QuoteBalanceSnapshot>;
60
+ deferClose(): Promise<void>;
61
+ private closeBuilder;
58
62
  close(): Promise<string>;
59
63
  }
package/dist/view/view.js CHANGED
@@ -26667,13 +26667,28 @@ class DepthView {
26667
26667
  await new Promise((resolve) => setTimeout(resolve, Math.min(pollMs, deadline - Date.now())));
26668
26668
  }
26669
26669
  }
26670
+ async deferClose() {
26671
+ const provider = this.program.provider;
26672
+ if (typeof provider.deferClose !== "function") {
26673
+ await this.close();
26674
+ return;
26675
+ }
26676
+ const snapshot = await this.status();
26677
+ if (snapshot.status === "pending")
26678
+ throw new Error("Private view computation is still pending");
26679
+ const instruction = await this.closeBuilder().instruction();
26680
+ provider.deferClose(this.id, instruction);
26681
+ }
26682
+ closeBuilder() {
26683
+ const builder = this.kind === "balance" ? this.program.methods.closeBalanceView() : this.kind === "market" ? this.program.methods.closeMarketView() : this.kind === "fill" ? this.program.methods.closeFillView() : this.program.methods.closeQuoteView();
26684
+ return builder.accountsPartial({ owner: this.owner, view: this.id });
26685
+ }
26670
26686
  async close() {
26671
26687
  const snapshot = await this.status();
26672
26688
  if (snapshot.status === "pending") {
26673
26689
  throw new Error("Private view computation is still pending");
26674
26690
  }
26675
- const builder = this.kind === "balance" ? this.program.methods.closeBalanceView() : this.kind === "market" ? this.program.methods.closeMarketView() : this.kind === "fill" ? this.program.methods.closeFillView() : this.program.methods.closeQuoteView();
26676
- return builder.accountsPartial({ owner: this.owner, view: this.id }).rpc({ commitment: "confirmed" });
26691
+ return this.closeBuilder().rpc({ commitment: "confirmed" });
26677
26692
  }
26678
26693
  }
26679
26694
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@depthhq/sdk",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -47,6 +47,9 @@
47
47
  "@depthhq/bindings": "0.1.1",
48
48
  "@depthhq/schema": "0.1.1"
49
49
  },
50
+ "devDependencies": {
51
+ "fake-indexeddb": "6.2.4"
52
+ },
50
53
  "scripts": {
51
54
  "build": "node ../../scripts/build-package.mjs sdk",
52
55
  "typecheck": "tsc --noEmit",