55import { Buffer } from "node:buffer" ;
66
77import { bcs } from "@mysten/sui/bcs" ;
8- import { SuiClient } from "@mysten/sui/client" ;
8+ import type { ClientWithCoreApi } from "@mysten/sui/client" ;
99import { Transaction } from "@mysten/sui/transactions" ;
1010import { SUI_CLOCK_OBJECT_ID } from "@mysten/sui/utils" ;
1111import 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