@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.
- package/CHANGELOG.md +52 -0
- package/dist/_virtual/rolldown_runtime.mjs +18 -0
- package/dist/client.d.mts +9 -6
- package/dist/client.d.mts.map +1 -1
- package/dist/client.mjs +3 -2
- package/dist/client.mjs.map +1 -1
- package/dist/contracts/deepbook/account.d.mts +18 -18
- package/dist/contracts/deepbook/account.d.mts.map +1 -1
- package/dist/contracts/deepbook/deep_price.d.mts +3 -3
- package/dist/contracts/deepbook/deep_price.d.mts.map +1 -1
- package/dist/contracts/deepbook_margin/margin_manager.mjs +1 -319
- package/dist/contracts/deepbook_margin/margin_manager.mjs.map +1 -1
- package/dist/contracts/deepbook_margin/margin_manager_upgraded.mjs +386 -0
- package/dist/contracts/deepbook_margin/margin_manager_upgraded.mjs.map +1 -0
- package/dist/contracts/deepbook_margin/oracle.mjs +9 -0
- package/dist/contracts/deepbook_margin/oracle.mjs.map +1 -1
- package/dist/contracts/deepbook_margin/pool_proxy.mjs +1 -383
- package/dist/contracts/deepbook_margin/pool_proxy.mjs.map +1 -1
- package/dist/contracts/deepbook_margin/pool_proxy_upgraded.mjs +340 -0
- package/dist/contracts/deepbook_margin/pool_proxy_upgraded.mjs.map +1 -0
- package/dist/contracts/margin_liquidation/liquidation_vault.mjs +33 -17
- package/dist/contracts/margin_liquidation/liquidation_vault.mjs.map +1 -1
- package/dist/index.d.mts +4 -3
- package/dist/index.mjs +2 -2
- package/dist/pyth/PriceServiceConnection.d.mts +18 -3
- package/dist/pyth/PriceServiceConnection.d.mts.map +1 -1
- package/dist/pyth/PriceServiceConnection.mjs +35 -4
- package/dist/pyth/PriceServiceConnection.mjs.map +1 -1
- package/dist/queries/priceFeedQueries.mjs +50 -18
- package/dist/queries/priceFeedQueries.mjs.map +1 -1
- package/dist/transactions/balanceManager.d.mts +12 -12
- package/dist/transactions/deepbook.d.mts +20 -20
- package/dist/transactions/deepbookAdmin.d.mts +4 -4
- package/dist/transactions/deepbookAdmin.d.mts.map +1 -1
- package/dist/transactions/marginAdmin.d.mts +7 -7
- package/dist/transactions/marginAdmin.d.mts.map +1 -1
- package/dist/transactions/marginAdmin.mjs +2 -2
- package/dist/transactions/marginAdmin.mjs.map +1 -1
- package/dist/transactions/marginLiquidations.d.mts.map +1 -1
- package/dist/transactions/marginLiquidations.mjs +19 -7
- package/dist/transactions/marginLiquidations.mjs.map +1 -1
- package/dist/transactions/marginMaintainer.d.mts +5 -5
- package/dist/transactions/marginManager.d.mts +32 -32
- package/dist/transactions/marginManager.d.mts.map +1 -1
- package/dist/transactions/marginManager.mjs +43 -34
- package/dist/transactions/marginManager.mjs.map +1 -1
- package/dist/transactions/marginPool.d.mts +18 -18
- package/dist/transactions/marginTPSL.d.mts +10 -10
- package/dist/transactions/marginTPSL.d.mts.map +1 -1
- package/dist/transactions/marginTPSL.mjs +19 -10
- package/dist/transactions/marginTPSL.mjs.map +1 -1
- package/dist/transactions/poolProxy.d.mts +8 -8
- package/dist/transactions/poolProxy.d.mts.map +1 -1
- package/dist/transactions/poolProxy.mjs +34 -27
- package/dist/transactions/poolProxy.mjs.map +1 -1
- package/dist/types/index.d.mts +16 -1
- package/dist/types/index.d.mts.map +1 -1
- package/dist/types/index.mjs.map +1 -1
- package/dist/utils/config.d.mts +30 -10
- package/dist/utils/config.d.mts.map +1 -1
- package/dist/utils/config.mjs +33 -4
- package/dist/utils/config.mjs.map +1 -1
- package/dist/utils/constants.d.mts +32 -1
- package/dist/utils/constants.d.mts.map +1 -1
- package/dist/utils/constants.mjs +54 -23
- package/dist/utils/constants.mjs.map +1 -1
- package/package.json +1 -1
- package/src/client.ts +9 -1
- package/src/contracts/deepbook_margin/margin_manager.ts +140 -10
- package/src/contracts/deepbook_margin/margin_manager_upgraded.ts +651 -0
- package/src/contracts/deepbook_margin/oracle.ts +59 -0
- package/src/contracts/deepbook_margin/pool_proxy.ts +600 -584
- package/src/contracts/deepbook_margin/pool_proxy_upgraded.ts +614 -0
- package/src/contracts/margin_liquidation/liquidation_vault.ts +142 -0
- package/src/index.ts +4 -0
- package/src/pyth/PriceServiceConnection.ts +69 -8
- package/src/queries/priceFeedQueries.ts +74 -24
- package/src/transactions/marginAdmin.ts +2 -4
- package/src/transactions/marginLiquidations.ts +20 -6
- package/src/transactions/marginManager.ts +43 -33
- package/src/transactions/marginTPSL.ts +19 -9
- package/src/transactions/poolProxy.ts +34 -30
- package/src/types/index.ts +16 -0
- package/src/utils/config.ts +69 -9
- 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
|
|
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
|
|
54
|
+
* @returns Array of base64-encoded update messages.
|
|
39
55
|
*/
|
|
40
56
|
async getLatestVaas(priceIds: HexString[]): Promise<string[]> {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
|
75
|
+
return this.#ctx.config.getPriceInfoObjectId(coinKey);
|
|
27
76
|
}
|
|
28
77
|
|
|
29
|
-
const
|
|
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.
|
|
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
|
|
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.
|
|
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
|
|
147
|
+
const feedIdToCoinKeys: Record<string, string[]> = {};
|
|
97
148
|
for (const coinKey of staleCoinKeys) {
|
|
98
|
-
const feedId = this.#ctx.config.
|
|
99
|
-
|
|
100
|
-
|
|
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
|
|
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
|
|
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
|
|
119
|
-
|
|
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.
|
|
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
|
-
|
|
416
|
-
throw new Error('Coin feed not found');
|
|
417
|
-
}
|
|
415
|
+
const feed = this.#config.getFeedId(coinKey);
|
|
418
416
|
const priceFeedInput = new Uint8Array(
|
|
419
|
-
hexToBytes(
|
|
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
|
-
|
|
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
|
|
119
|
-
quoteOracle: quoteCoin
|
|
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
|
-
|
|
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
|
|
162
|
-
quoteOracle: quoteCoin
|
|
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,
|