Skip to content

Commit 552e7b4

Browse files
committed
feat(pyth-sui-js): migrate to @mysten/sui v2 API
Migrate @pythnetwork/pyth-sui-js, the Sui CLI tooling, and contract_manager from @mysten/sui v1 to v2. Key changes: - Replace SuiClient with ClientWithCoreApi (transport-agnostic) in pyth-sui-js - Use provider.core.* namespace for all data access methods - BCS-encode dynamic field names as required by v2 - Update response handling for new object/json format - Move @mysten/sui to peerDependencies (^2.0.0) in pyth-sui-js - Update CLI to use SuiJsonRpcClient and fromBase64 - Migrate contract_manager to SuiJsonRpcClient for compatibility - Update contract_manager tsconfig moduleResolution to bundler - Narrow catch in getPriceFeedObjectId to only swallow not-found errors - Bump pyth-sui-js version from 3.0.0 to 4.0.0 Closes #3454
1 parent 059f890 commit 552e7b4

10 files changed

Lines changed: 335 additions & 131 deletions

File tree

contract_manager/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"@injectivelabs/networks": "^1.14.6",
99
"@iota/iota-sdk": "^0.5.0",
1010
"@mysten/move-bytecode-template": "^0.3.0",
11-
"@mysten/sui": "^1.3.0",
11+
"@mysten/sui": "^2.0.0",
1212
"@pythnetwork/client": "catalog:",
1313
"@pythnetwork/cosmwasm-deploy-tools": "workspace:*",
1414
"@pythnetwork/entropy-sdk-solidity": "workspace:*",

contract_manager/src/core/chains.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ import type {
1313
MoveStruct as SuiMoveStruct,
1414
MoveValue as SuiMoveValue,
1515
SuiTransactionBlockResponseOptions,
16-
} from "@mysten/sui/client";
17-
import { SuiClient } from "@mysten/sui/client";
16+
} from "@mysten/sui/jsonRpc";
17+
import { SuiJsonRpcClient } from "@mysten/sui/jsonRpc";
1818
import { Ed25519Keypair as SuiEd25519Keypair } from "@mysten/sui/keypairs/ed25519";
1919
import { Transaction as SuiTransaction } from "@mysten/sui/transactions";
2020
import {
@@ -394,8 +394,11 @@ export class SuiChain extends Chain {
394394
).encode();
395395
}
396396

397-
getProvider(): SuiClient {
398-
return new SuiClient({ url: this.rpcUrl });
397+
getProvider(): SuiJsonRpcClient {
398+
return new SuiJsonRpcClient({
399+
url: this.rpcUrl,
400+
network: this.mainnet ? "mainnet" : "testnet",
401+
});
399402
}
400403

401404
getAccountAddress(privateKey: PrivateKey): Promise<string> {
@@ -611,7 +614,7 @@ export class SuiChain extends Chain {
611614
* `{ .., upgrade_cap: UpgradeCap }` convention.
612615
*/
613616
async getStatePackageInfo(
614-
client: SuiClient,
617+
client: SuiJsonRpcClient,
615618
stateId: string,
616619
): Promise<{
617620
package: string;
@@ -658,7 +661,7 @@ export class SuiChain extends Chain {
658661
}
659662

660663
private async getStateObject(
661-
client: SuiClient,
664+
client: SuiJsonRpcClient,
662665
stateId: string,
663666
): Promise<SuiMoveStruct> {
664667
const { data: stateObject, error } = await client.getObject({

contract_manager/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"compilerOptions": {
33
"module": "preserve",
4-
"moduleResolution": "node",
4+
"moduleResolution": "bundler",
55
"target": "ES2020",
66
},
77
"exclude": ["node_modules", "**/__tests__/*"],

pnpm-lock.yaml

Lines changed: 211 additions & 43 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

target_chains/sui/cli/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"dependencies": {
33
"@certusone/wormhole-sdk": "^0.9.12",
4-
"@mysten/sui": "^1.3.0",
4+
"@mysten/sui": "^2.0.0",
55
"@pythnetwork/contract-manager": "workspace:*",
66
"@pythnetwork/price-service-client": "^1.4.0",
77
"@pythnetwork/price-service-sdk": "^1.2.0",

target_chains/sui/cli/src/pyth_deploy.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
import { Transaction } from "@mysten/sui/transactions";
22

3-
import { MIST_PER_SUI, normalizeSuiObjectId, fromB64 } from "@mysten/sui/utils";
3+
import {
4+
MIST_PER_SUI,
5+
normalizeSuiObjectId,
6+
fromBase64,
7+
} from "@mysten/sui/utils";
48

59
import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519";
610
import { execSync } from "child_process";
7-
import { SuiClient } from "@mysten/sui/client";
11+
import { SuiJsonRpcClient } from "@mysten/sui/jsonRpc";
812
import { bcs } from "@mysten/sui/bcs";
913
import type { DataSource } from "@pythnetwork/xc-admin-common/governance_payload/SetDataSources";
1014

1115
export async function publishPackage(
1216
keypair: Ed25519Keypair,
13-
provider: SuiClient,
17+
provider: SuiJsonRpcClient,
1418
packagePath: string,
1519
): Promise<{ packageId: string; upgradeCapId: string; deployerCapId: string }> {
1620
// Build contracts
@@ -34,7 +38,9 @@ export async function publishPackage(
3438
txb.setGasBudget(MIST_PER_SUI / 2n); // 0.5 SUI
3539

3640
const [upgradeCap] = txb.publish({
37-
modules: buildOutput.modules.map((m: string) => Array.from(fromB64(m))),
41+
modules: buildOutput.modules.map((m: string) =>
42+
Array.from(fromBase64(m)),
43+
),
3844
dependencies: buildOutput.dependencies.map((d: string) =>
3945
normalizeSuiObjectId(d),
4046
),
@@ -97,7 +103,7 @@ export async function publishPackage(
97103

98104
export async function initPyth(
99105
keypair: Ed25519Keypair,
100-
provider: SuiClient,
106+
provider: SuiJsonRpcClient,
101107
pythPackageId: string,
102108
deployerCapId: string,
103109
upgradeCapId: string,

target_chains/sui/cli/src/upgrade_pyth.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { Transaction } from "@mysten/sui/transactions";
2-
import { fromB64, MIST_PER_SUI, normalizeSuiObjectId } from "@mysten/sui/utils";
3-
import { SuiClient } from "@mysten/sui/client";
2+
import {
3+
fromBase64,
4+
MIST_PER_SUI,
5+
normalizeSuiObjectId,
6+
} from "@mysten/sui/utils";
7+
import { SuiJsonRpcClient } from "@mysten/sui/jsonRpc";
48
import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519";
59

610
import { execSync } from "child_process";
@@ -18,7 +22,9 @@ export function buildForBytecodeAndDigest(packagePath: string) {
1822
),
1923
);
2024
return {
21-
modules: buildOutput.modules.map((m: string) => Array.from(fromB64(m))),
25+
modules: buildOutput.modules.map((m: string) =>
26+
Array.from(fromBase64(m)),
27+
),
2228
dependencies: buildOutput.dependencies.map((d: string) =>
2329
normalizeSuiObjectId(d),
2430
),
@@ -28,7 +34,7 @@ export function buildForBytecodeAndDigest(packagePath: string) {
2834

2935
export async function upgradePyth(
3036
keypair: Ed25519Keypair,
31-
provider: SuiClient,
37+
provider: SuiJsonRpcClient,
3238
modules: number[][],
3339
dependencies: string[],
3440
signedVaa: Buffer,
@@ -78,7 +84,7 @@ export async function upgradePyth(
7884

7985
export async function migratePyth(
8086
keypair: Ed25519Keypair,
81-
provider: SuiClient,
87+
provider: SuiJsonRpcClient,
8288
signedUpgradeVaa: Buffer,
8389
contract: SuiPriceFeedContract,
8490
pythPackageOld: string,

target_chains/sui/sdk/js/package.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@
33
"name": "Pyth Data Association"
44
},
55
"dependencies": {
6-
"@mysten/sui": "^1.3.0",
76
"@pythnetwork/hermes-client": "workspace:*",
87
"buffer": "^6.0.3"
98
},
9+
"peerDependencies": {
10+
"@mysten/sui": "^2.0.0"
11+
},
1012
"description": "Pyth Network Sui Utilities",
1113
"devDependencies": {
14+
"@mysten/sui": "^2.0.0",
1215
"@pythnetwork/jest-config": "workspace:",
1316
"@truffle/hdwallet-provider": "^2.1.5",
1417
"@types/ethereum-protocol": "^1.0.2",
@@ -86,5 +89,5 @@
8689
},
8790
"type": "module",
8891
"types": "./dist/cjs/index.d.ts",
89-
"version": "3.0.0"
90-
}
92+
"version": "4.0.0"
93+
}

target_chains/sui/sdk/js/src/client.ts

Lines changed: 69 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import { Buffer } from "node:buffer";
66

77
import { bcs } from "@mysten/sui/bcs";
8-
import { SuiClient } from "@mysten/sui/client";
8+
import type { ClientWithCoreApi } from "@mysten/sui/client";
99
import { Transaction } from "@mysten/sui/transactions";
1010
import { SUI_CLOCK_OBJECT_ID } from "@mysten/sui/utils";
1111
import type { HexString } from "@pythnetwork/hermes-client";
@@ -24,7 +24,7 @@ export class SuiPythClient {
2424
private priceFeedObjectIdCache = new Map<HexString, ObjectId>();
2525
private baseUpdateFee: number | undefined;
2626
constructor(
27-
public provider: SuiClient,
27+
public provider: ClientWithCoreApi,
2828
public pythStateId: ObjectId,
2929
public wormholeStateId: ObjectId,
3030
) {
@@ -34,18 +34,14 @@ export class SuiPythClient {
3434

3535
async getBaseUpdateFee(): Promise<number> {
3636
if (this.baseUpdateFee === undefined) {
37-
const result = await this.provider.getObject({
38-
id: this.pythStateId,
39-
options: { showContent: true },
37+
const result = await this.provider.core.getObject({
38+
objectId: this.pythStateId,
39+
include: { json: true },
4040
});
41-
if (
42-
!result.data?.content ||
43-
result.data.content.dataType !== "moveObject"
44-
)
41+
const json = result.object.json as Record<string, unknown> | null;
42+
if (!json)
4543
throw new Error("Unable to fetch pyth state object");
46-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
47-
// @ts-ignore
48-
this.baseUpdateFee = result.data.content.fields.base_update_fee as number;
44+
this.baseUpdateFee = Number(json.base_update_fee);
4945
}
5046

5147
return this.baseUpdateFee;
@@ -58,27 +54,23 @@ export class SuiPythClient {
5854
* @returns package id
5955
*/
6056
async getPackageId(objectId: ObjectId): Promise<ObjectId> {
61-
const state = await this.provider
62-
.getObject({
63-
id: objectId,
64-
options: {
65-
showContent: true,
66-
},
67-
})
68-
.then((result) => {
69-
if (result.data?.content?.dataType == "moveObject") {
70-
return result.data.content.fields;
71-
}
72-
console.log(result.data?.content);
73-
74-
throw new Error(`Cannot fetch package id for object ${objectId}`);
75-
});
57+
const result = await this.provider.core.getObject({
58+
objectId,
59+
include: { json: true },
60+
});
61+
const json = result.object.json as Record<string, unknown> | null;
62+
if (!json) {
63+
throw new Error(`Cannot fetch package id for object ${objectId}`);
64+
}
7665

77-
if ("upgrade_cap" in state) {
78-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
79-
// @ts-ignore
80-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access
81-
return state.upgrade_cap.fields.package;
66+
if ("upgrade_cap" in json) {
67+
const upgradeCap = json.upgrade_cap as Record<string, unknown>;
68+
// JSON-RPC wraps nested objects in { type, fields }, gRPC may not.
69+
const fields = (upgradeCap.fields ?? upgradeCap) as Record<
70+
string,
71+
unknown
72+
>;
73+
return fields.package as string;
8274
}
8375

8476
throw new Error("upgrade_cap not found");
@@ -299,28 +291,43 @@ export class SuiPythClient {
299291
const normalizedFeedId = feedId.replace("0x", "");
300292
if (!this.priceFeedObjectIdCache.has(normalizedFeedId)) {
301293
const { id: tableId, fieldType } = await this.getPriceTableInfo();
302-
const result = await this.provider.getDynamicFieldObject({
303-
parentId: tableId,
304-
name: {
305-
type: `${fieldType}::price_identifier::PriceIdentifier`,
306-
value: {
307-
bytes: [...Buffer.from(normalizedFeedId, "hex")],
294+
// BCS-encode the PriceIdentifier struct name.
295+
// PriceIdentifier has a single field `bytes: vector<u8>`, so its BCS
296+
// encoding is the same as just encoding the inner vector<u8>.
297+
const nameBcs = bcs
298+
.vector(bcs.U8)
299+
.serialize([...Buffer.from(normalizedFeedId, "hex")])
300+
.toBytes();
301+
try {
302+
const result = await this.provider.core.getDynamicObjectField({
303+
parentId: tableId,
304+
name: {
305+
type: `${fieldType}::price_identifier::PriceIdentifier`,
306+
bcs: nameBcs,
308307
},
309-
},
310-
});
311-
if (!result.data?.content) {
312-
return undefined;
313-
}
314-
if (result.data.content.dataType !== "moveObject") {
315-
throw new Error("Price feed type mismatch");
308+
include: { json: true },
309+
});
310+
const json = result.object.json as Record<string, unknown> | null;
311+
if (!json) {
312+
return undefined;
313+
}
314+
this.priceFeedObjectIdCache.set(
315+
normalizedFeedId,
316+
json.value as string,
317+
);
318+
} catch (e: unknown) {
319+
// Only treat "not found" errors as missing feed; re-throw others
320+
// (e.g. network timeouts, auth failures) so callers see real outages.
321+
const msg = e instanceof Error ? e.message : String(e);
322+
if (
323+
msg.includes("Could not find the referenced object") ||
324+
msg.includes("dynamicFieldNotFound") ||
325+
msg.includes("not found")
326+
) {
327+
return undefined;
328+
}
329+
throw e;
316330
}
317-
this.priceFeedObjectIdCache.set(
318-
normalizedFeedId,
319-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
320-
// @ts-ignore
321-
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
322-
result.data.content.fields.value,
323-
);
324331
}
325332
return this.priceFeedObjectIdCache.get(normalizedFeedId);
326333
}
@@ -331,24 +338,29 @@ export class SuiPythClient {
331338
*/
332339
async getPriceTableInfo(): Promise<{ id: ObjectId; fieldType: ObjectId }> {
333340
if (this.priceTableInfo === undefined) {
334-
const result = await this.provider.getDynamicFieldObject({
341+
// BCS-encode the "price_info" name as vector<u8>
342+
const nameBcs = bcs
343+
.vector(bcs.U8)
344+
.serialize([...Buffer.from("price_info")])
345+
.toBytes();
346+
const result = await this.provider.core.getDynamicObjectField({
335347
parentId: this.pythStateId,
336348
name: {
337349
type: "vector<u8>",
338-
value: "price_info",
350+
bcs: nameBcs,
339351
},
340352
});
341-
if (!result.data?.type) {
353+
if (!result.object.type) {
342354
throw new Error(
343355
"Price Table not found, contract may not be initialized",
344356
);
345357
}
346-
let type = result.data.type.replace("0x2::table::Table<", "");
358+
let type = result.object.type.replace("0x2::table::Table<", "");
347359
type = type.replace(
348360
"::price_identifier::PriceIdentifier, 0x2::object::ID>",
349361
"",
350362
);
351-
this.priceTableInfo = { id: result.data.objectId, fieldType: type };
363+
this.priceTableInfo = { id: result.object.objectId, fieldType: type };
352364
}
353365
return this.priceTableInfo;
354366
}

0 commit comments

Comments
 (0)