@mysten/deepbook-v3 1.6.7 → 2.0.0

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.
Files changed (85) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/dist/_virtual/rolldown_runtime.mjs +18 -0
  3. package/dist/client.d.mts +9 -6
  4. package/dist/client.d.mts.map +1 -1
  5. package/dist/client.mjs +3 -2
  6. package/dist/client.mjs.map +1 -1
  7. package/dist/contracts/deepbook/account.d.mts +18 -18
  8. package/dist/contracts/deepbook/account.d.mts.map +1 -1
  9. package/dist/contracts/deepbook/deep_price.d.mts +3 -3
  10. package/dist/contracts/deepbook/deep_price.d.mts.map +1 -1
  11. package/dist/contracts/deepbook_margin/margin_manager.mjs +1 -319
  12. package/dist/contracts/deepbook_margin/margin_manager.mjs.map +1 -1
  13. package/dist/contracts/deepbook_margin/margin_manager_upgraded.mjs +386 -0
  14. package/dist/contracts/deepbook_margin/margin_manager_upgraded.mjs.map +1 -0
  15. package/dist/contracts/deepbook_margin/oracle.mjs +9 -0
  16. package/dist/contracts/deepbook_margin/oracle.mjs.map +1 -1
  17. package/dist/contracts/deepbook_margin/pool_proxy.mjs +1 -383
  18. package/dist/contracts/deepbook_margin/pool_proxy.mjs.map +1 -1
  19. package/dist/contracts/deepbook_margin/pool_proxy_upgraded.mjs +340 -0
  20. package/dist/contracts/deepbook_margin/pool_proxy_upgraded.mjs.map +1 -0
  21. package/dist/contracts/margin_liquidation/liquidation_vault.mjs +33 -17
  22. package/dist/contracts/margin_liquidation/liquidation_vault.mjs.map +1 -1
  23. package/dist/index.d.mts +4 -3
  24. package/dist/index.mjs +2 -2
  25. package/dist/pyth/PriceServiceConnection.d.mts +18 -3
  26. package/dist/pyth/PriceServiceConnection.d.mts.map +1 -1
  27. package/dist/pyth/PriceServiceConnection.mjs +35 -4
  28. package/dist/pyth/PriceServiceConnection.mjs.map +1 -1
  29. package/dist/queries/priceFeedQueries.mjs +50 -18
  30. package/dist/queries/priceFeedQueries.mjs.map +1 -1
  31. package/dist/transactions/balanceManager.d.mts +12 -12
  32. package/dist/transactions/deepbook.d.mts +20 -20
  33. package/dist/transactions/deepbookAdmin.d.mts +4 -4
  34. package/dist/transactions/deepbookAdmin.d.mts.map +1 -1
  35. package/dist/transactions/marginAdmin.d.mts +7 -7
  36. package/dist/transactions/marginAdmin.d.mts.map +1 -1
  37. package/dist/transactions/marginAdmin.mjs +2 -2
  38. package/dist/transactions/marginAdmin.mjs.map +1 -1
  39. package/dist/transactions/marginLiquidations.d.mts.map +1 -1
  40. package/dist/transactions/marginLiquidations.mjs +19 -7
  41. package/dist/transactions/marginLiquidations.mjs.map +1 -1
  42. package/dist/transactions/marginMaintainer.d.mts +5 -5
  43. package/dist/transactions/marginManager.d.mts +32 -32
  44. package/dist/transactions/marginManager.d.mts.map +1 -1
  45. package/dist/transactions/marginManager.mjs +43 -34
  46. package/dist/transactions/marginManager.mjs.map +1 -1
  47. package/dist/transactions/marginPool.d.mts +18 -18
  48. package/dist/transactions/marginTPSL.d.mts +10 -10
  49. package/dist/transactions/marginTPSL.d.mts.map +1 -1
  50. package/dist/transactions/marginTPSL.mjs +19 -10
  51. package/dist/transactions/marginTPSL.mjs.map +1 -1
  52. package/dist/transactions/poolProxy.d.mts +8 -8
  53. package/dist/transactions/poolProxy.d.mts.map +1 -1
  54. package/dist/transactions/poolProxy.mjs +34 -27
  55. package/dist/transactions/poolProxy.mjs.map +1 -1
  56. package/dist/types/index.d.mts +16 -1
  57. package/dist/types/index.d.mts.map +1 -1
  58. package/dist/types/index.mjs.map +1 -1
  59. package/dist/utils/config.d.mts +30 -10
  60. package/dist/utils/config.d.mts.map +1 -1
  61. package/dist/utils/config.mjs +33 -4
  62. package/dist/utils/config.mjs.map +1 -1
  63. package/dist/utils/constants.d.mts +32 -1
  64. package/dist/utils/constants.d.mts.map +1 -1
  65. package/dist/utils/constants.mjs +54 -23
  66. package/dist/utils/constants.mjs.map +1 -1
  67. package/package.json +1 -1
  68. package/src/client.ts +9 -1
  69. package/src/contracts/deepbook_margin/margin_manager.ts +140 -10
  70. package/src/contracts/deepbook_margin/margin_manager_upgraded.ts +651 -0
  71. package/src/contracts/deepbook_margin/oracle.ts +59 -0
  72. package/src/contracts/deepbook_margin/pool_proxy.ts +600 -584
  73. package/src/contracts/deepbook_margin/pool_proxy_upgraded.ts +614 -0
  74. package/src/contracts/margin_liquidation/liquidation_vault.ts +142 -0
  75. package/src/index.ts +4 -0
  76. package/src/pyth/PriceServiceConnection.ts +69 -8
  77. package/src/queries/priceFeedQueries.ts +74 -24
  78. package/src/transactions/marginAdmin.ts +2 -4
  79. package/src/transactions/marginLiquidations.ts +20 -6
  80. package/src/transactions/marginManager.ts +43 -33
  81. package/src/transactions/marginTPSL.ts +19 -9
  82. package/src/transactions/poolProxy.ts +34 -30
  83. package/src/types/index.ts +16 -0
  84. package/src/utils/config.ts +69 -9
  85. package/src/utils/constants.ts +88 -25
@@ -282,6 +282,7 @@ export interface LiquidateBaseOptions {
282
282
  ];
283
283
  typeArguments: [string, string];
284
284
  }
285
+ /** Twin: `liquidate_base_upgraded`. Edit both. */
285
286
  export function liquidateBase(options: LiquidateBaseOptions) {
286
287
  const packageAddress = options.package ?? '@deepbook/margin-liquidation';
287
288
  const argumentsTypes = [
@@ -344,6 +345,7 @@ export interface LiquidateQuoteOptions {
344
345
  ];
345
346
  typeArguments: [string, string];
346
347
  }
348
+ /** Twin: `liquidate_quote_upgraded`. Edit both. */
347
349
  export function liquidateQuote(options: LiquidateQuoteOptions) {
348
350
  const packageAddress = options.package ?? '@deepbook/margin-liquidation';
349
351
  const argumentsTypes = [
@@ -399,3 +401,143 @@ export function balance(options: BalanceOptions) {
399
401
  typeArguments: options.typeArguments,
400
402
  });
401
403
  }
404
+ export interface LiquidateBaseUpgradedArguments {
405
+ self: RawTransactionArgument<string>;
406
+ marginManager: RawTransactionArgument<string>;
407
+ registry: RawTransactionArgument<string>;
408
+ baseOracle: RawTransactionArgument<string>;
409
+ quoteOracle: RawTransactionArgument<string>;
410
+ baseMarginPool: RawTransactionArgument<string>;
411
+ quoteMarginPool: RawTransactionArgument<string>;
412
+ pool: RawTransactionArgument<string>;
413
+ repayAmount: RawTransactionArgument<number | bigint | null>;
414
+ }
415
+ export interface LiquidateBaseUpgradedOptions {
416
+ package?: string;
417
+ arguments:
418
+ | LiquidateBaseUpgradedArguments
419
+ | [
420
+ self: RawTransactionArgument<string>,
421
+ marginManager: RawTransactionArgument<string>,
422
+ registry: RawTransactionArgument<string>,
423
+ baseOracle: RawTransactionArgument<string>,
424
+ quoteOracle: RawTransactionArgument<string>,
425
+ baseMarginPool: RawTransactionArgument<string>,
426
+ quoteMarginPool: RawTransactionArgument<string>,
427
+ pool: RawTransactionArgument<string>,
428
+ repayAmount: RawTransactionArgument<number | bigint | null>,
429
+ ];
430
+ typeArguments: [string, string];
431
+ }
432
+ /**
433
+ * `liquidate_base` against Pyth's upgraded Core.
434
+ *
435
+ * Pyth is replacing Core with a separately published package, so its
436
+ * `PriceInfoObject` is a distinct Move type and `liquidate_base`'s frozen
437
+ * signature can never accept it. Once Pyth stops publishing legacy Core the legacy
438
+ * entry aborts on staleness by itself, and this becomes the only way the vault can
439
+ * liquidate. The gate (`should_liquidate`) and the settlement
440
+ * (`settle_base_liquidation`) are shared with the legacy entry; the body between
441
+ * them is duplicated, because the two `PriceInfoObject` types cannot be unified.
442
+ * Twin: `liquidate_base`. Edit both.
443
+ */
444
+ export function liquidateBaseUpgraded(options: LiquidateBaseUpgradedOptions) {
445
+ const packageAddress = options.package ?? '@deepbook/margin-liquidation';
446
+ const argumentsTypes = [
447
+ null,
448
+ null,
449
+ null,
450
+ null,
451
+ null,
452
+ null,
453
+ null,
454
+ null,
455
+ '0x1::option::Option<u64>',
456
+ '0x2::clock::Clock',
457
+ ] satisfies (string | null)[];
458
+ const parameterNames = [
459
+ 'self',
460
+ 'marginManager',
461
+ 'registry',
462
+ 'baseOracle',
463
+ 'quoteOracle',
464
+ 'baseMarginPool',
465
+ 'quoteMarginPool',
466
+ 'pool',
467
+ 'repayAmount',
468
+ ];
469
+ return (tx: Transaction) =>
470
+ tx.moveCall({
471
+ package: packageAddress,
472
+ module: 'liquidation_vault',
473
+ function: 'liquidate_base_upgraded',
474
+ arguments: normalizeMoveArguments(options.arguments, argumentsTypes, parameterNames),
475
+ typeArguments: options.typeArguments,
476
+ });
477
+ }
478
+ export interface LiquidateQuoteUpgradedArguments {
479
+ self: RawTransactionArgument<string>;
480
+ marginManager: RawTransactionArgument<string>;
481
+ registry: RawTransactionArgument<string>;
482
+ baseOracle: RawTransactionArgument<string>;
483
+ quoteOracle: RawTransactionArgument<string>;
484
+ baseMarginPool: RawTransactionArgument<string>;
485
+ quoteMarginPool: RawTransactionArgument<string>;
486
+ pool: RawTransactionArgument<string>;
487
+ repayAmount: RawTransactionArgument<number | bigint | null>;
488
+ }
489
+ export interface LiquidateQuoteUpgradedOptions {
490
+ package?: string;
491
+ arguments:
492
+ | LiquidateQuoteUpgradedArguments
493
+ | [
494
+ self: RawTransactionArgument<string>,
495
+ marginManager: RawTransactionArgument<string>,
496
+ registry: RawTransactionArgument<string>,
497
+ baseOracle: RawTransactionArgument<string>,
498
+ quoteOracle: RawTransactionArgument<string>,
499
+ baseMarginPool: RawTransactionArgument<string>,
500
+ quoteMarginPool: RawTransactionArgument<string>,
501
+ pool: RawTransactionArgument<string>,
502
+ repayAmount: RawTransactionArgument<number | bigint | null>,
503
+ ];
504
+ typeArguments: [string, string];
505
+ }
506
+ /**
507
+ * `liquidate_quote` against Pyth's upgraded Core. See `liquidate_base_upgraded`.
508
+ * Twin: `liquidate_quote`. Edit both.
509
+ */
510
+ export function liquidateQuoteUpgraded(options: LiquidateQuoteUpgradedOptions) {
511
+ const packageAddress = options.package ?? '@deepbook/margin-liquidation';
512
+ const argumentsTypes = [
513
+ null,
514
+ null,
515
+ null,
516
+ null,
517
+ null,
518
+ null,
519
+ null,
520
+ null,
521
+ '0x1::option::Option<u64>',
522
+ '0x2::clock::Clock',
523
+ ] satisfies (string | null)[];
524
+ const parameterNames = [
525
+ 'self',
526
+ 'marginManager',
527
+ 'registry',
528
+ 'baseOracle',
529
+ 'quoteOracle',
530
+ 'baseMarginPool',
531
+ 'quoteMarginPool',
532
+ 'pool',
533
+ 'repayAmount',
534
+ ];
535
+ return (tx: Transaction) =>
536
+ tx.moveCall({
537
+ package: packageAddress,
538
+ module: 'liquidation_vault',
539
+ function: 'liquidate_quote_upgraded',
540
+ arguments: normalizeMoveArguments(options.arguments, argumentsTypes, parameterNames),
541
+ typeArguments: options.typeArguments,
542
+ });
543
+ }
package/src/index.ts CHANGED
@@ -23,6 +23,7 @@ export { MarginTPSLContract } from './transactions/marginTPSL.js';
23
23
 
24
24
  // Pyth price feed integration
25
25
  export { SuiPythClient, SuiPriceServiceConnection } from './pyth/pyth.js';
26
+ export type { PriceServiceConnectionConfig } from './pyth/PriceServiceConnection.js';
26
27
 
27
28
  // BCS types for parsing on-chain data
28
29
  export { Account, Balances, Order, OrderDeepPrice, VecSet } from './types/bcs.js';
@@ -34,6 +35,7 @@ export type {
34
35
  Pool,
35
36
  MarginManager,
36
37
  MarginPool,
38
+ PythConfig,
37
39
  Config,
38
40
  } from './types/index.js';
39
41
 
@@ -106,6 +108,8 @@ export {
106
108
  testnetPackageIds,
107
109
  mainnetPythConfigs,
108
110
  testnetPythConfigs,
111
+ PYTH_UPGRADED_HERMES,
112
+ DEEPBOOK_HERMES_PROXY,
109
113
  } from './utils/constants.js';
110
114
  export {
111
115
  DEEP_SCALAR,
@@ -12,6 +12,15 @@ export type PriceFeedRequestConfig = {
12
12
  export type PriceServiceConnectionConfig = {
13
13
  timeout?: number;
14
14
  httpRetries?: number;
15
+ /**
16
+ * Bearer token for Hermes deployments that authenticate. The endpoint serving Pyth's
17
+ * upgraded Core answers 401 without one.
18
+ *
19
+ * `accessToken` is chosen to converge with the in-flight `@mysten/suins` Pyth migration
20
+ * (ts-sdks#1158), which takes the same credential under this name. That is not yet
21
+ * published, so this is a convergence target rather than an existing convention.
22
+ */
23
+ accessToken?: string;
15
24
  };
16
25
  export class PriceServiceConnection {
17
26
  private httpClient: AxiosInstance;
@@ -25,6 +34,7 @@ export class PriceServiceConnection {
25
34
  this.httpClient = axios.create({
26
35
  baseURL: endpoint,
27
36
  timeout: config?.timeout || 5000,
37
+ headers: config?.accessToken ? { Authorization: `Bearer ${config.accessToken}` } : undefined,
28
38
  });
29
39
  axiosRetry(this.httpClient, {
30
40
  retries: config?.httpRetries || 3,
@@ -32,17 +42,68 @@ export class PriceServiceConnection {
32
42
  });
33
43
  }
34
44
  /**
35
- * Fetch latest VAAs of given price IDs.
45
+ * Fetch the latest price update data for the given price IDs.
46
+ *
47
+ * Uses Hermes v2 (`/v2/updates/price/latest`). The v1 endpoint (`/api/latest_vaas`)
48
+ * is deprecated; it returned the same payload this reads out of `binary.data`.
49
+ *
50
+ * Hermes returns one accumulator message covering every requested feed, not one per
51
+ * feed, so the result is normally a single element regardless of `priceIds.length`.
36
52
  *
37
53
  * @param priceIds Array of hex-encoded price IDs.
38
- * @returns Array of base64 encoded VAAs.
54
+ * @returns Array of base64-encoded update messages.
39
55
  */
40
56
  async getLatestVaas(priceIds: HexString[]): Promise<string[]> {
41
- const response = await this.httpClient.get('/api/latest_vaas', {
42
- params: {
43
- ids: priceIds,
44
- },
45
- });
46
- return response.data;
57
+ let response;
58
+ try {
59
+ response = await this.httpClient.get('/v2/updates/price/latest', {
60
+ params: {
61
+ // Serialized explicitly rather than relying on axios's array encoding, which is
62
+ // what Hermes expects and what a future axios major could change under us.
63
+ 'ids[]': priceIds,
64
+ encoding: 'base64',
65
+ parsed: false,
66
+ },
67
+ });
68
+ } catch (error) {
69
+ // An axios error carries `config.headers` — including `Authorization` — and both
70
+ // `JSON.stringify(err)` and `err.toJSON()` serialize it. Callers log failed requests,
71
+ // so letting the raw error escape would put the bearer token in their logs. Re-throw
72
+ // a plain error carrying only what is useful for diagnosis.
73
+ throw new Error(`Hermes request failed: ${describeRequestError(error)}`, {
74
+ cause: undefined,
75
+ });
76
+ }
77
+
78
+ const data = response.data?.binary?.data;
79
+ if (!Array.isArray(data)) {
80
+ throw new Error(
81
+ `Unexpected Hermes response: expected 'binary.data' array from /v2/updates/price/latest, got ${JSON.stringify(
82
+ response.data,
83
+ )?.slice(0, 200)}`,
84
+ );
85
+ }
86
+
87
+ return data;
88
+ }
89
+ }
90
+
91
+ /**
92
+ * A diagnosable one-line summary of a failed request that cannot contain the access token:
93
+ * status and response body only, never the request config or its headers.
94
+ */
95
+ function describeRequestError(error: unknown): string {
96
+ const e = error as {
97
+ response?: { status?: number; statusText?: string; data?: unknown };
98
+ code?: string;
99
+ message?: string;
100
+ };
101
+ if (e?.response) {
102
+ const body =
103
+ typeof e.response.data === 'string' ? e.response.data : JSON.stringify(e.response.data);
104
+ return `${e.response.status ?? '?'} ${e.response.statusText ?? ''} ${body?.slice(0, 200) ?? ''}`.trim();
47
105
  }
106
+ // No response: a transport failure. `message` is a fixed axios string ("timeout of 5000ms
107
+ // exceeded", "Network Error") that embeds no header material.
108
+ return e?.code ? `${e.code} ${e.message ?? ''}`.trim() : (e?.message ?? 'unknown error');
48
109
  }
@@ -6,6 +6,8 @@ import type { Transaction } from '@mysten/sui/transactions';
6
6
  import { PriceInfoObject } from '../contracts/pyth/price_info.js';
7
7
  import { SuiPriceServiceConnection, SuiPythClient } from '../pyth/pyth.js';
8
8
  import { PRICE_INFO_OBJECT_MAX_AGE_MS } from '../utils/config.js';
9
+ import { DEEPBOOK_HERMES_PROXY, PYTH_UPGRADED_HERMES } from '../utils/constants.js';
10
+ import { ConfigurationError } from '../utils/errors.js';
9
11
  import type { QueryContext } from './context.js';
10
12
 
11
13
  export class PriceFeedQueries {
@@ -15,6 +17,53 @@ export class PriceFeedQueries {
15
17
  this.#ctx = ctx;
16
18
  }
17
19
 
20
+ /**
21
+ * The Hermes endpoint serving the Pyth deployment margin is configured against.
22
+ *
23
+ * There are two routes, chosen by whether the caller brought
24
+ * credentials. With `accessToken` set, the SDK talks to Pyth directly and no DeepBook
25
+ * infrastructure is in the path — the token is sent to Pyth's own host, so a token
26
+ * minted for some other endpoint must be paired with an explicit `hermesEndpoint`.
27
+ *
28
+ * Note the converse too: an explicit `hermesEndpoint` wins over everything, and the
29
+ * token is sent to whatever host it names. Point it at a mirror or staging proxy while
30
+ * a production token is still configured and the credential goes there.
31
+ *
32
+ * Without a token it falls back to the DeepBook-operated proxy, which supplies
33
+ * credentials server-side; that proxy is not deployed yet, so today this path throws a
34
+ * `ConfigurationError` instead. An explicit `hermesEndpoint` overrides both.
35
+ */
36
+ #hermesEndpoint(): string {
37
+ const { hermesEndpoint, accessToken } = this.#ctx.config.pyth;
38
+ if (hermesEndpoint) {
39
+ return hermesEndpoint;
40
+ }
41
+
42
+ if (accessToken) {
43
+ return PYTH_UPGRADED_HERMES;
44
+ }
45
+ if (DEEPBOOK_HERMES_PROXY) {
46
+ return DEEPBOOK_HERMES_PROXY;
47
+ }
48
+
49
+ throw new ConfigurationError(
50
+ "Pushing price updates against Pyth's upgraded Core needs credentials: its Hermes answers 401 unauthenticated. Set the client's `pythAccessToken` option (or `pyth.accessToken`), or point `pyth.hermesEndpoint` at an endpoint that supplies credentials itself.",
51
+ );
52
+ }
53
+
54
+ /**
55
+ * A Hermes connection for the active Pyth deployment, carrying any configured auth
56
+ * headers. The endpoint serving the upgraded Core requires an `Authorization` header
57
+ * and answers 401 without one.
58
+ */
59
+ #connection(): SuiPriceServiceConnection {
60
+ const { accessToken } = this.#ctx.config.pyth;
61
+ return new SuiPriceServiceConnection(
62
+ this.#hermesEndpoint(),
63
+ accessToken ? { accessToken } : undefined,
64
+ );
65
+ }
66
+
18
67
  async getPriceInfoObject(tx: Transaction, coinKey: string): Promise<string> {
19
68
  this.#ctx.config.requirePyth();
20
69
  const currentTime = Date.now();
@@ -23,21 +72,16 @@ export class PriceFeedQueries {
23
72
  priceInfoObjectAge &&
24
73
  currentTime - priceInfoObjectAge * 1000 < PRICE_INFO_OBJECT_MAX_AGE_MS
25
74
  ) {
26
- return await this.#ctx.config.getCoin(coinKey).priceInfoObjectId!;
75
+ return this.#ctx.config.getPriceInfoObjectId(coinKey);
27
76
  }
28
77
 
29
- const endpoint =
30
- this.#ctx.config.network === 'testnet'
31
- ? 'https://hermes-beta.pyth.network'
32
- : 'https://hermes.pyth.network';
33
- const connection = new SuiPriceServiceConnection(endpoint);
78
+ const connection = this.#connection();
34
79
 
35
- const priceIDs = [this.#ctx.config.getCoin(coinKey).feed!];
80
+ const priceIDs = [this.#ctx.config.getFeedId(coinKey)];
36
81
 
37
82
  const priceUpdateData = await connection.getPriceFeedsUpdateData(priceIDs);
38
83
 
39
- const wormholeStateId = this.#ctx.config.pyth.wormholeStateId;
40
- const pythStateId = this.#ctx.config.pyth.pythStateId;
84
+ const { pythStateId, wormholeStateId } = this.#ctx.config.pyth;
41
85
 
42
86
  const client = new SuiPythClient(this.#ctx.client, pythStateId, wormholeStateId);
43
87
 
@@ -55,7 +99,7 @@ export class PriceFeedQueries {
55
99
  const coinToObjectId: Record<string, string> = {};
56
100
  const objectIds: string[] = [];
57
101
  for (const coinKey of coinKeys) {
58
- const priceInfoObjectId = this.#ctx.config.getCoin(coinKey).priceInfoObjectId!;
102
+ const priceInfoObjectId = this.#ctx.config.getPriceInfoObjectId(coinKey);
59
103
  coinToObjectId[coinKey] = priceInfoObjectId;
60
104
  objectIds.push(priceInfoObjectId);
61
105
  }
@@ -92,38 +136,44 @@ export class PriceFeedQueries {
92
136
  return result;
93
137
  }
94
138
 
139
+ // Distinct coins can share a feed. No shipped map has a collision — every configured
140
+ // coin is on its own feed — but `coins` is a public constructor option, and coins
141
+ // tracking the same underlying (a wrapped asset priced off its reference, say) are
142
+ // the normal reason to supply one. Deduplicate before building the update:
143
+ // a feed listed twice would emit two `update_single_price_feed` calls against the same
144
+ // object off one hot-potato vector, and pay two update fees for it. One feed can also
145
+ // map to several coins, so the reverse index holds a list, not a single key.
95
146
  const staleFeedIds: string[] = [];
96
- const feedIdToCoinKey: Record<string, string> = {};
147
+ const feedIdToCoinKeys: Record<string, string[]> = {};
97
148
  for (const coinKey of staleCoinKeys) {
98
- const feedId = this.#ctx.config.getCoin(coinKey).feed!;
99
- staleFeedIds.push(feedId);
100
- feedIdToCoinKey[feedId] = coinKey;
149
+ const feedId = this.#ctx.config.getFeedId(coinKey);
150
+ if (!feedIdToCoinKeys[feedId]) {
151
+ feedIdToCoinKeys[feedId] = [];
152
+ staleFeedIds.push(feedId);
153
+ }
154
+ feedIdToCoinKeys[feedId].push(coinKey);
101
155
  }
102
156
 
103
- const endpoint =
104
- this.#ctx.config.network === 'testnet'
105
- ? 'https://hermes-beta.pyth.network'
106
- : 'https://hermes.pyth.network';
107
- const connection = new SuiPriceServiceConnection(endpoint);
157
+ const connection = this.#connection();
108
158
 
109
159
  const priceUpdateData = await connection.getPriceFeedsUpdateData(staleFeedIds);
110
160
 
111
- const wormholeStateId = this.#ctx.config.pyth.wormholeStateId;
112
- const pythStateId = this.#ctx.config.pyth.pythStateId;
161
+ const { pythStateId, wormholeStateId } = this.#ctx.config.pyth;
113
162
  const pythClient = new SuiPythClient(this.#ctx.client, pythStateId, wormholeStateId);
114
163
 
115
164
  const updatedObjectIds = await pythClient.updatePriceFeeds(tx, priceUpdateData, staleFeedIds);
116
165
 
117
166
  for (let i = 0; i < staleFeedIds.length; i++) {
118
- const coinKey = feedIdToCoinKey[staleFeedIds[i]];
119
- result[coinKey] = updatedObjectIds[i];
167
+ for (const coinKey of feedIdToCoinKeys[staleFeedIds[i]]) {
168
+ result[coinKey] = updatedObjectIds[i];
169
+ }
120
170
  }
121
171
 
122
172
  return result;
123
173
  }
124
174
 
125
175
  async getPriceInfoObjectAge(coinKey: string): Promise<number> {
126
- const priceInfoObjectId = this.#ctx.config.getCoin(coinKey).priceInfoObjectId!;
176
+ const priceInfoObjectId = this.#ctx.config.getPriceInfoObjectId(coinKey);
127
177
  const res = await this.#ctx.client.core.getObject({
128
178
  objectId: priceInfoObjectId,
129
179
  include: {
@@ -412,11 +412,9 @@ export class MarginAdminContract {
412
412
  newCoinTypeData =
413
413
  (coinKey: string, maxConfBps: number, maxEwmaDifferenceBps: number) => (tx: Transaction) => {
414
414
  const coin = this.#config.getCoin(coinKey);
415
- if (!coin.feed) {
416
- throw new Error('Coin feed not found');
417
- }
415
+ const feed = this.#config.getFeedId(coinKey);
418
416
  const priceFeedInput = new Uint8Array(
419
- hexToBytes(coin['feed']!.startsWith('0x') ? coin.feed!.slice(2) : coin['feed']),
417
+ hexToBytes(feed.startsWith('0x') ? feed.slice(2) : feed),
420
418
  );
421
419
  return tx.add(
422
420
  oracleMoveCalls.newCoinTypeDataFromCurrency({
@@ -20,6 +20,20 @@ export class MarginLiquidationsContract {
20
20
  this.#config = config;
21
21
  }
22
22
 
23
+ /**
24
+ * The oracle-taking liquidation entrypoints. Unlike the margin package,
25
+ * `margin_liquidation` puts the upgraded entries in the same module as parallel
26
+ * functions rather than in a parallel module, so these name a function rather than a
27
+ * namespace. The vault's own deposit/withdraw/balance take no oracle and are unchanged.
28
+ */
29
+ get #liquidateBaseCall() {
30
+ return liquidationVaultMoveCalls.liquidateBaseUpgraded;
31
+ }
32
+
33
+ get #liquidateQuoteCall() {
34
+ return liquidationVaultMoveCalls.liquidateQuoteUpgraded;
35
+ }
36
+
23
37
  /**
24
38
  * @description Create a new liquidation vault
25
39
  * @param {string} liquidationAdminCap The liquidation admin cap object ID
@@ -109,14 +123,14 @@ export class MarginLiquidationsContract {
109
123
  : tx.pure.option('u64', null);
110
124
 
111
125
  tx.add(
112
- liquidationVaultMoveCalls.liquidateBase({
126
+ this.#liquidateBaseCall({
113
127
  package: this.#config.LIQUIDATION_PACKAGE_ID,
114
128
  arguments: {
115
129
  self: vaultId,
116
130
  marginManager: managerAddress,
117
131
  registry: this.#config.MARGIN_REGISTRY_ID,
118
- baseOracle: baseCoin.priceInfoObjectId!,
119
- quoteOracle: quoteCoin.priceInfoObjectId!,
132
+ baseOracle: this.#config.getPriceInfoObjectId(pool.baseCoin),
133
+ quoteOracle: this.#config.getPriceInfoObjectId(pool.quoteCoin),
120
134
  baseMarginPool: baseMarginPool.address,
121
135
  quoteMarginPool: quoteMarginPool.address,
122
136
  pool: pool.address,
@@ -152,14 +166,14 @@ export class MarginLiquidationsContract {
152
166
  : tx.pure.option('u64', null);
153
167
 
154
168
  tx.add(
155
- liquidationVaultMoveCalls.liquidateQuote({
169
+ this.#liquidateQuoteCall({
156
170
  package: this.#config.LIQUIDATION_PACKAGE_ID,
157
171
  arguments: {
158
172
  self: vaultId,
159
173
  marginManager: managerAddress,
160
174
  registry: this.#config.MARGIN_REGISTRY_ID,
161
- baseOracle: baseCoin.priceInfoObjectId!,
162
- quoteOracle: quoteCoin.priceInfoObjectId!,
175
+ baseOracle: this.#config.getPriceInfoObjectId(pool.baseCoin),
176
+ quoteOracle: this.#config.getPriceInfoObjectId(pool.quoteCoin),
163
177
  baseMarginPool: baseMarginPool.address,
164
178
  quoteMarginPool: quoteMarginPool.address,
165
179
  pool: pool.address,