chore(target_chains/sui): update sui js packages (#1340)

* chore(target_chains/sui): update sui js packages

* chore: bump version

---------

Co-authored-by: Amin Moghaddam <amin@pyth.network>
This commit is contained in:
Ali Behjati 2024-03-01 19:35:52 +03:30 committed by GitHub
parent f9de5430d1
commit 736a20208b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 1880 additions and 2949 deletions

View File

@ -23,7 +23,7 @@
"dependencies": { "dependencies": {
"@certusone/wormhole-sdk": "^0.9.8", "@certusone/wormhole-sdk": "^0.9.8",
"@injectivelabs/networks": "1.0.68", "@injectivelabs/networks": "1.0.68",
"@mysten/sui.js": "^0.37.1", "@mysten/sui.js": "^0.49.1",
"@pythnetwork/cosmwasm-deploy-tools": "*", "@pythnetwork/cosmwasm-deploy-tools": "*",
"@pythnetwork/entropy-sdk-solidity": "*", "@pythnetwork/entropy-sdk-solidity": "*",
"@pythnetwork/price-service-client": "*", "@pythnetwork/price-service-client": "*",
@ -32,7 +32,7 @@
"aptos": "^1.5.0", "aptos": "^1.5.0",
"bs58": "^5.0.0", "bs58": "^5.0.0",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"typescript": "^4.9.3" "typescript": "^5.3.3"
}, },
"devDependencies": { "devDependencies": {
"prettier": "^2.6.2", "prettier": "^2.6.2",

View File

@ -20,12 +20,8 @@ import {
InjectiveExecutor, InjectiveExecutor,
} from "@pythnetwork/cosmwasm-deploy-tools"; } from "@pythnetwork/cosmwasm-deploy-tools";
import { Network } from "@injectivelabs/networks"; import { Network } from "@injectivelabs/networks";
import { import { SuiClient } from "@mysten/sui.js/client";
Connection, import { Ed25519Keypair } from "@mysten/sui.js/keypairs/ed25519";
Ed25519Keypair,
JsonRpcProvider,
RawSigner,
} from "@mysten/sui.js";
export type ChainConfig = Record<string, string> & { export type ChainConfig = Record<string, string> & {
mainnet: boolean; mainnet: boolean;
@ -290,17 +286,15 @@ export class SuiChain extends Chain {
).encode(); ).encode();
} }
getProvider(): JsonRpcProvider { getProvider(): SuiClient {
return new JsonRpcProvider(new Connection({ fullnode: this.rpcUrl })); return new SuiClient({ url: this.rpcUrl });
} }
async getAccountAddress(privateKey: PrivateKey): Promise<string> { async getAccountAddress(privateKey: PrivateKey): Promise<string> {
const provider = this.getProvider();
const keypair = Ed25519Keypair.fromSecretKey( const keypair = Ed25519Keypair.fromSecretKey(
Buffer.from(privateKey, "hex") Buffer.from(privateKey, "hex")
); );
const wallet = new RawSigner(keypair, provider); return keypair.toSuiAddress();
return await wallet.getAddress();
} }
async getAccountBalance(privateKey: PrivateKey): Promise<number> { async getAccountBalance(privateKey: PrivateKey): Promise<number> {

View File

@ -1,14 +1,12 @@
import {
Ed25519Keypair,
ObjectId,
RawSigner,
SUI_CLOCK_OBJECT_ID,
TransactionBlock,
} from "@mysten/sui.js";
import { Chain, SuiChain } from "../chains"; import { Chain, SuiChain } from "../chains";
import { DataSource } from "xc_admin_common"; import { DataSource } from "xc_admin_common";
import { PriceFeedContract, PrivateKey, TxResult } from "../base"; import { PriceFeedContract, PrivateKey, TxResult } from "../base";
import { SuiPythClient } from "@pythnetwork/pyth-sui-js"; import { SuiPythClient } from "@pythnetwork/pyth-sui-js";
import { SUI_CLOCK_OBJECT_ID } from "@mysten/sui.js/utils";
import { Ed25519Keypair } from "@mysten/sui.js/keypairs/ed25519";
import { TransactionBlock } from "@mysten/sui.js/transactions";
type ObjectId = string;
export class SuiPriceFeedContract extends PriceFeedContract { export class SuiPriceFeedContract extends PriceFeedContract {
static type = "SuiPriceFeedContract"; static type = "SuiPriceFeedContract";
@ -96,13 +94,6 @@ export class SuiPriceFeedContract extends PriceFeedContract {
timestamp: string; timestamp: string;
}; };
}) { }) {
const packageId = await this.getPythPackageId();
const expectedType = `${packageId}::price::Price`;
if (priceInfo.type !== expectedType) {
throw new Error(
`Price type mismatch, expected ${expectedType} but found ${priceInfo.type}`
);
}
let expo = priceInfo.fields.expo.fields.magnitude; let expo = priceInfo.fields.expo.fields.magnitude;
if (priceInfo.fields.expo.fields.negative) expo = "-" + expo; if (priceInfo.fields.expo.fields.negative) expo = "-" + expo;
let price = priceInfo.fields.price.fields.magnitude; let price = priceInfo.fields.price.fields.magnitude;
@ -135,10 +126,14 @@ export class SuiPriceFeedContract extends PriceFeedContract {
} }
return { return {
emaPrice: await this.parsePrice( emaPrice: await this.parsePrice(
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
priceInfo.data.content.fields.price_info.fields.price_feed.fields priceInfo.data.content.fields.price_info.fields.price_feed.fields
.ema_price .ema_price
), ),
price: await this.parsePrice( price: await this.parsePrice(
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
priceInfo.data.content.fields.price_info.fields.price_feed.fields.price priceInfo.data.content.fields.price_info.fields.price_feed.fields.price
), ),
}; };
@ -303,21 +298,25 @@ export class SuiPriceFeedContract extends PriceFeedContract {
keypair: Ed25519Keypair keypair: Ed25519Keypair
) { ) {
const provider = this.getProvider(); const provider = this.getProvider();
const txBlock = { tx.setSender(keypair.toSuiAddress());
const dryRun = await provider.dryRunTransactionBlock({
transactionBlock: await tx.build({ client: provider }),
});
tx.setGasBudget(BigInt(dryRun.input.gasData.budget.toString()) * BigInt(2));
return provider.signAndExecuteTransactionBlock({
signer: keypair,
transactionBlock: tx, transactionBlock: tx,
options: { options: {
showEffects: true, showEffects: true,
showEvents: true, showEvents: true,
}, },
}; });
const wallet = new RawSigner(keypair, provider);
const gasCost = await wallet.getGasCostEstimation(txBlock);
tx.setGasBudget(gasCost * BigInt(2));
return wallet.signAndExecuteTransactionBlock(txBlock);
} }
async getValidTimePeriod() { async getValidTimePeriod() {
const fields = await this.getStateFields(); const fields = await this.getStateFields();
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return Number(fields.stale_price_threshold); return Number(fields.stale_price_threshold);
} }
@ -338,6 +337,8 @@ export class SuiPriceFeedContract extends PriceFeedContract {
if (result.data.content.dataType !== "moveObject") { if (result.data.content.dataType !== "moveObject") {
throw new Error("Data Sources type mismatch"); throw new Error("Data Sources type mismatch");
} }
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return result.data.content.fields.value.fields.keys.map( return result.data.content.fields.value.fields.keys.map(
({ ({
fields, fields,
@ -359,6 +360,8 @@ export class SuiPriceFeedContract extends PriceFeedContract {
async getGovernanceDataSource(): Promise<DataSource> { async getGovernanceDataSource(): Promise<DataSource> {
const fields = await this.getStateFields(); const fields = await this.getStateFields();
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const governanceFields = fields.governance_data_source.fields; const governanceFields = fields.governance_data_source.fields;
const chainId = governanceFields.emitter_chain; const chainId = governanceFields.emitter_chain;
const emitterAddress = const emitterAddress =
@ -371,11 +374,15 @@ export class SuiPriceFeedContract extends PriceFeedContract {
async getBaseUpdateFee() { async getBaseUpdateFee() {
const fields = await this.getStateFields(); const fields = await this.getStateFields();
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return { amount: fields.base_update_fee }; return { amount: fields.base_update_fee };
} }
async getLastExecutedGovernanceSequence() { async getLastExecutedGovernanceSequence() {
const fields = await this.getStateFields(); const fields = await this.getStateFields();
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return Number(fields.last_executed_governance_sequence); return Number(fields.last_executed_governance_sequence);
} }

View File

@ -1,15 +1,7 @@
- chain: sui_mainnet
stateId: "0xf9ff3ef935ef6cdfb659a203bf2754cebeb63346e29114a535ea6f41315e5a3f"
wormholeStateId: "0xaeab97f96cf9877fee2883315d459552b2b921edc16d7ceac6eab944dd88919c"
type: SuiPriceFeedContract
- chain: sui_mainnet - chain: sui_mainnet
stateId: "0x1f9310238ee9298fb703c3419030b35b22bb1cc37113e3bb5007c99aec79e5b8" stateId: "0x1f9310238ee9298fb703c3419030b35b22bb1cc37113e3bb5007c99aec79e5b8"
wormholeStateId: "0xaeab97f96cf9877fee2883315d459552b2b921edc16d7ceac6eab944dd88919c" wormholeStateId: "0xaeab97f96cf9877fee2883315d459552b2b921edc16d7ceac6eab944dd88919c"
type: SuiPriceFeedContract type: SuiPriceFeedContract
- chain: sui_testnet
stateId: "0xb3142a723792001caafc601b7c6fe38f09f3684e360b56d8d90fc574e71e75f3"
wormholeStateId: "0xebba4cc4d614f7a7cdbe883acc76d1cc767922bc96778e7b68be0d15fce27c02"
type: SuiPriceFeedContract
- chain: sui_testnet - chain: sui_testnet
stateId: "0x2d82612a354f0b7e52809fc2845642911c7190404620cec8688f68808f8800d8" stateId: "0x2d82612a354f0b7e52809fc2845642911c7190404620cec8688f68808f8800d8"
wormholeStateId: "0xebba4cc4d614f7a7cdbe883acc76d1cc767922bc96778e7b68be0d15fce27c02" wormholeStateId: "0xebba4cc4d614f7a7cdbe883acc76d1cc767922bc96778e7b68be0d15fce27c02"

4398
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "@pythnetwork/price-pusher", "name": "@pythnetwork/price-pusher",
"version": "6.2.0", "version": "6.3.0",
"description": "Pyth Price Pusher", "description": "Pyth Price Pusher",
"homepage": "https://pyth.network", "homepage": "https://pyth.network",
"main": "lib/index.js", "main": "lib/index.js",
@ -45,14 +45,14 @@
"@typescript-eslint/eslint-plugin": "^5.20.0", "@typescript-eslint/eslint-plugin": "^5.20.0",
"@typescript-eslint/parser": "^5.20.0", "@typescript-eslint/parser": "^5.20.0",
"eslint": "^8.13.0", "eslint": "^8.13.0",
"jest": "^27.5.1", "jest": "^29.7.0",
"prettier": "^2.6.2", "prettier": "^2.6.2",
"ts-jest": "^27.1.4", "ts-jest": "^29.1.1",
"typescript": "^4.6.3" "typescript": "^5.3.3"
}, },
"dependencies": { "dependencies": {
"@injectivelabs/sdk-ts": "1.10.72", "@injectivelabs/sdk-ts": "1.10.72",
"@mysten/sui.js": "^0.37.1", "@mysten/sui.js": "^0.49.1",
"@pythnetwork/price-service-client": "*", "@pythnetwork/price-service-client": "*",
"@pythnetwork/pyth-sdk-solidity": "*", "@pythnetwork/pyth-sdk-solidity": "*",
"@pythnetwork/pyth-sui-js": "*", "@pythnetwork/pyth-sui-js": "*",

View File

@ -6,7 +6,7 @@ import { PythPriceListener } from "../pyth-price-listener";
import { Controller } from "../controller"; import { Controller } from "../controller";
import { Options } from "yargs"; import { Options } from "yargs";
import { SuiPriceListener, SuiPricePusher } from "./sui"; import { SuiPriceListener, SuiPricePusher } from "./sui";
import { Ed25519Keypair } from "@mysten/sui.js"; import { Ed25519Keypair } from "@mysten/sui.js/keypairs/ed25519";
export default { export default {
command: "sui", command: "sui",

View File

@ -6,30 +6,22 @@ import {
} from "../interface"; } from "../interface";
import { DurationInSeconds } from "../utils"; import { DurationInSeconds } from "../utils";
import { PriceServiceConnection } from "@pythnetwork/price-service-client"; import { PriceServiceConnection } from "@pythnetwork/price-service-client";
import {
JsonRpcProvider,
Connection,
Ed25519Keypair,
RawSigner,
TransactionBlock,
getCreatedObjects,
SuiObjectRef,
getTransactionEffects,
getExecutionStatusError,
PaginatedCoins,
SuiAddress,
ObjectId,
} from "@mysten/sui.js";
import { SuiPythClient } from "@pythnetwork/pyth-sui-js"; import { SuiPythClient } from "@pythnetwork/pyth-sui-js";
import { Ed25519Keypair } from "@mysten/sui.js/keypairs/ed25519";
import { TransactionBlock } from "@mysten/sui.js/transactions";
import { SuiClient, SuiObjectRef, PaginatedCoins } from "@mysten/sui.js/client";
const GAS_FEE_FOR_SPLIT = 2_000_000_000; const GAS_FEE_FOR_SPLIT = 2_000_000_000;
// TODO: read this from on chain config // TODO: read this from on chain config
const MAX_NUM_GAS_OBJECTS_IN_PTB = 256; const MAX_NUM_GAS_OBJECTS_IN_PTB = 256;
const MAX_NUM_OBJECTS_IN_ARGUMENT = 510; const MAX_NUM_OBJECTS_IN_ARGUMENT = 510;
type ObjectId = string;
type SuiAddress = string;
export class SuiPriceListener extends ChainPriceListener { export class SuiPriceListener extends ChainPriceListener {
private pythClient: SuiPythClient; private pythClient: SuiPythClient;
private provider: JsonRpcProvider; private provider: SuiClient;
constructor( constructor(
pythStateId: ObjectId, pythStateId: ObjectId,
@ -41,7 +33,7 @@ export class SuiPriceListener extends ChainPriceListener {
} }
) { ) {
super("sui", config.pollingFrequency, priceItems); super("sui", config.pollingFrequency, priceItems);
this.provider = new JsonRpcProvider(new Connection({ fullnode: endpoint })); this.provider = new SuiClient({ url: endpoint });
this.pythClient = new SuiPythClient( this.pythClient = new SuiPythClient(
this.provider, this.provider,
pythStateId, pythStateId,
@ -64,26 +56,22 @@ export class SuiPriceListener extends ChainPriceListener {
options: { showContent: true }, options: { showContent: true },
}); });
if ( if (!priceInfoObject.data || !priceInfoObject.data.content)
priceInfoObject.data === undefined ||
priceInfoObject.data.content === undefined
)
throw new Error("Price not found on chain for price id " + priceId); throw new Error("Price not found on chain for price id " + priceId);
if (priceInfoObject.data.content.dataType !== "moveObject") if (priceInfoObject.data.content.dataType !== "moveObject")
throw new Error("fetched object datatype should be moveObject"); throw new Error("fetched object datatype should be moveObject");
const { magnitude, negative } = const priceInfo =
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
priceInfoObject.data.content.fields.price_info.fields.price_feed.fields priceInfoObject.data.content.fields.price_info.fields.price_feed.fields
.price.fields.price.fields; .price.fields;
const { magnitude, negative } = priceInfo.price.fields;
const conf = const conf = priceInfo.conf;
priceInfoObject.data.content.fields.price_info.fields.price_feed.fields
.price.fields.conf;
const timestamp = const timestamp = priceInfo.timestamp;
priceInfoObject.data.content.fields.price_info.fields.price_feed.fields
.price.fields.timestamp;
return { return {
price: negative ? "-" + magnitude : magnitude, price: negative ? "-" + magnitude : magnitude,
@ -114,7 +102,8 @@ export class SuiPriceListener extends ChainPriceListener {
*/ */
export class SuiPricePusher implements IPricePusher { export class SuiPricePusher implements IPricePusher {
constructor( constructor(
private readonly signer: RawSigner, private readonly signer: Ed25519Keypair,
private readonly provider: SuiClient,
private priceServiceConnection: PriceServiceConnection, private priceServiceConnection: PriceServiceConnection,
private pythPackageId: string, private pythPackageId: string,
private pythStateId: string, private pythStateId: string,
@ -135,7 +124,7 @@ export class SuiPricePusher implements IPricePusher {
* @returns package id * @returns package id
*/ */
static async getPackageId( static async getPackageId(
provider: JsonRpcProvider, provider: SuiClient,
objectId: ObjectId objectId: ObjectId
): Promise<ObjectId> { ): Promise<ObjectId> {
const state = await provider const state = await provider
@ -154,6 +143,8 @@ export class SuiPricePusher implements IPricePusher {
}); });
if ("upgrade_cap" in state) { if ("upgrade_cap" in state) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return state.upgrade_cap.fields.package; return state.upgrade_cap.fields.package;
} }
@ -179,10 +170,7 @@ export class SuiPricePusher implements IPricePusher {
); );
} }
const provider = new JsonRpcProvider( const provider = new SuiClient({ url: endpoint });
new Connection({ fullnode: endpoint })
);
const signer = new RawSigner(keypair, provider);
const pythPackageId = await SuiPricePusher.getPackageId( const pythPackageId = await SuiPricePusher.getPackageId(
provider, provider,
pythStateId pythStateId
@ -193,7 +181,8 @@ export class SuiPricePusher implements IPricePusher {
); );
const gasPool = await SuiPricePusher.initializeGasPool( const gasPool = await SuiPricePusher.initializeGasPool(
signer, keypair,
provider,
numGasObjects numGasObjects
); );
@ -204,7 +193,8 @@ export class SuiPricePusher implements IPricePusher {
); );
return new SuiPricePusher( return new SuiPricePusher(
signer, keypair,
provider,
priceServiceConnection, priceServiceConnection,
pythPackageId, pythPackageId,
pythStateId, pythStateId,
@ -282,15 +272,16 @@ export class SuiPricePusher implements IPricePusher {
try { try {
tx.setGasPayment([gasObject]); tx.setGasPayment([gasObject]);
tx.setGasBudget(this.gasBudget); tx.setGasBudget(this.gasBudget);
const result = await this.signer.signAndExecuteTransactionBlock({ const result = await this.provider.signAndExecuteTransactionBlock({
signer: this.signer,
transactionBlock: tx, transactionBlock: tx,
options: { options: {
showEffects: true, showEffects: true,
}, },
}); });
nextGasObject = getTransactionEffects(result) nextGasObject = result.effects?.mutated
?.mutated?.map((obj) => obj.reference) ?.map((obj) => obj.reference)
.find((ref) => ref.objectId === gasObject.objectId); .find((ref) => ref.objectId === gasObject.objectId);
console.log( console.log(
@ -308,7 +299,7 @@ export class SuiPricePusher implements IPricePusher {
} else { } else {
// Refresh the coin object here in case the error is caused by an object version mismatch. // Refresh the coin object here in case the error is caused by an object version mismatch.
nextGasObject = await SuiPricePusher.tryRefreshObjectReference( nextGasObject = await SuiPricePusher.tryRefreshObjectReference(
this.signer.provider, this.provider,
gasObject gasObject
); );
} }
@ -328,16 +319,18 @@ export class SuiPricePusher implements IPricePusher {
// This function will smash all coins owned by the signer into one, and then // This function will smash all coins owned by the signer into one, and then
// split them equally into numGasObjects. // split them equally into numGasObjects.
private static async initializeGasPool( private static async initializeGasPool(
signer: RawSigner, signer: Ed25519Keypair,
provider: SuiClient,
numGasObjects: number numGasObjects: number
): Promise<SuiObjectRef[]> { ): Promise<SuiObjectRef[]> {
const signerAddress = await signer.getAddress(); const signerAddress = await signer.toSuiAddress();
const consolidatedCoin = await SuiPricePusher.mergeGasCoinsIntoOne( const consolidatedCoin = await SuiPricePusher.mergeGasCoinsIntoOne(
signer, signer,
provider,
signerAddress signerAddress
); );
const coinResult = await signer.provider.getObject({ const coinResult = await provider.getObject({
id: consolidatedCoin.objectId, id: consolidatedCoin.objectId,
options: { showContent: true }, options: { showContent: true },
}); });
@ -347,6 +340,8 @@ export class SuiPricePusher implements IPricePusher {
coinResult.data.content && coinResult.data.content &&
coinResult.data.content.dataType == "moveObject" coinResult.data.content.dataType == "moveObject"
) { ) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
balance = coinResult.data.content.fields.balance; balance = coinResult.data.content.fields.balance;
} else throw new Error("Bad coin object"); } else throw new Error("Bad coin object");
const splitAmount = const splitAmount =
@ -354,6 +349,7 @@ export class SuiPricePusher implements IPricePusher {
const gasPool = await SuiPricePusher.splitGasCoinEqually( const gasPool = await SuiPricePusher.splitGasCoinEqually(
signer, signer,
provider,
signerAddress, signerAddress,
Number(splitAmount), Number(splitAmount),
numGasObjects, numGasObjects,
@ -367,16 +363,16 @@ export class SuiPricePusher implements IPricePusher {
// of the object. Return the provided object reference if an error occurs or the object could not // of the object. Return the provided object reference if an error occurs or the object could not
// be retrieved. // be retrieved.
private static async tryRefreshObjectReference( private static async tryRefreshObjectReference(
provider: JsonRpcProvider, provider: SuiClient,
ref: SuiObjectRef ref: SuiObjectRef
): Promise<SuiObjectRef> { ): Promise<SuiObjectRef> {
try { try {
const objectResponse = await provider.getObject({ id: ref.objectId }); const objectResponse = await provider.getObject({ id: ref.objectId });
if (objectResponse.data !== undefined) { if (objectResponse.data !== undefined) {
return { return {
digest: objectResponse.data.digest, digest: objectResponse.data!.digest,
objectId: objectResponse.data.objectId, objectId: objectResponse.data!.objectId,
version: objectResponse.data.version, version: objectResponse.data!.version,
}; };
} else { } else {
return ref; return ref;
@ -387,7 +383,7 @@ export class SuiPricePusher implements IPricePusher {
} }
private static async getAllGasCoins( private static async getAllGasCoins(
provider: JsonRpcProvider, provider: SuiClient,
owner: SuiAddress owner: SuiAddress
): Promise<SuiObjectRef[]> { ): Promise<SuiObjectRef[]> {
let hasNextPage = true; let hasNextPage = true;
@ -420,7 +416,8 @@ export class SuiPricePusher implements IPricePusher {
} }
private static async splitGasCoinEqually( private static async splitGasCoinEqually(
signer: RawSigner, signer: Ed25519Keypair,
provider: SuiClient,
signerAddress: SuiAddress, signerAddress: SuiAddress,
splitAmount: number, splitAmount: number,
numGasObjects: number, numGasObjects: number,
@ -438,17 +435,18 @@ export class SuiPricePusher implements IPricePusher {
tx.pure(signerAddress) tx.pure(signerAddress)
); );
tx.setGasPayment([gasCoin]); tx.setGasPayment([gasCoin]);
const result = await signer.signAndExecuteTransactionBlock({ const result = await provider.signAndExecuteTransactionBlock({
signer,
transactionBlock: tx, transactionBlock: tx,
options: { showEffects: true }, options: { showEffects: true },
}); });
const error = getExecutionStatusError(result); const error = result?.effects?.status.error;
if (error) { if (error) {
throw new Error( throw new Error(
`Failed to initialize gas pool: ${error}. Try re-running the script` `Failed to initialize gas pool: ${error}. Try re-running the script`
); );
} }
const newCoins = getCreatedObjects(result)!.map((obj) => obj.reference); const newCoins = result.effects!.created!.map((obj) => obj.reference);
if (newCoins.length !== numGasObjects) { if (newCoins.length !== numGasObjects) {
throw new Error( throw new Error(
`Failed to initialize gas pool. Expected ${numGasObjects}, got: ${newCoins}` `Failed to initialize gas pool. Expected ${numGasObjects}, got: ${newCoins}`
@ -458,13 +456,11 @@ export class SuiPricePusher implements IPricePusher {
} }
private static async mergeGasCoinsIntoOne( private static async mergeGasCoinsIntoOne(
signer: RawSigner, signer: Ed25519Keypair,
provider: SuiClient,
owner: SuiAddress owner: SuiAddress
): Promise<SuiObjectRef> { ): Promise<SuiObjectRef> {
const gasCoins = await SuiPricePusher.getAllGasCoins( const gasCoins = await SuiPricePusher.getAllGasCoins(provider, owner);
signer.provider,
owner
);
// skip merging if there is only one coin // skip merging if there is only one coin
if (gasCoins.length === 1) { if (gasCoins.length === 1) {
return gasCoins[0]; return gasCoins[0];
@ -486,7 +482,8 @@ export class SuiPricePusher implements IPricePusher {
mergeTx.setGasPayment(coins); mergeTx.setGasPayment(coins);
let mergeResult; let mergeResult;
try { try {
mergeResult = await signer.signAndExecuteTransactionBlock({ mergeResult = await provider.signAndExecuteTransactionBlock({
signer,
transactionBlock: mergeTx, transactionBlock: mergeTx,
options: { showEffects: true }, options: { showEffects: true },
}); });
@ -507,15 +504,13 @@ export class SuiPricePusher implements IPricePusher {
} }
throw e; throw e;
} }
const error = getExecutionStatusError(mergeResult); const error = mergeResult?.effects?.status.error;
if (error) { if (error) {
throw new Error( throw new Error(
`Failed to merge coins when initializing gas pool: ${error}. Try re-running the script` `Failed to merge coins when initializing gas pool: ${error}. Try re-running the script`
); );
} }
finalCoin = getTransactionEffects(mergeResult)!.mutated!.map( finalCoin = mergeResult.effects!.mutated!.map((obj) => obj.reference)[0];
(obj) => obj.reference
)[0];
} }
return finalCoin as SuiObjectRef; return finalCoin as SuiObjectRef;

View File

@ -12,9 +12,10 @@
"author": "", "author": "",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"rimraf": "^5.0.0",
"@pythnetwork/cosmwasm-deploy-tools": "*", "@pythnetwork/cosmwasm-deploy-tools": "*",
"contract_manager": "*", "contract_manager": "*",
"rimraf": "^5.0.0",
"typescript": "^5.3.3",
"yargs": "^17.0.1" "yargs": "^17.0.1"
}, },
"devDependencies": { "devDependencies": {

View File

@ -1,6 +1,6 @@
{ {
"name": "@pythnetwork/pyth-sui-js", "name": "@pythnetwork/pyth-sui-js",
"version": "1.2.5", "version": "2.0.0",
"description": "Pyth Network Sui Utilities", "description": "Pyth Network Sui Utilities",
"homepage": "https://pyth.network", "homepage": "https://pyth.network",
"author": { "author": {
@ -48,13 +48,13 @@
"jest": "^29.4.1", "jest": "^29.4.1",
"prettier": "^2.6.2", "prettier": "^2.6.2",
"ts-jest": "^29.0.5", "ts-jest": "^29.0.5",
"typescript": "^4.6.3", "typescript": "^5.3.3",
"web3": "^1.8.2", "web3": "^1.8.2",
"yargs": "^17.0.20" "yargs": "^17.0.20"
}, },
"dependencies": { "dependencies": {
"@mysten/sui.js": "^0.49.1",
"@pythnetwork/price-service-client": "*", "@pythnetwork/price-service-client": "*",
"@mysten/sui.js": "^0.37.1",
"buffer": "^6.0.3" "buffer": "^6.0.3"
} }
} }

View File

@ -1,18 +1,12 @@
import { import { SuiClient } from "@mysten/sui.js/client";
builder, import { SUI_CLOCK_OBJECT_ID } from "@mysten/sui.js/utils";
JsonRpcProvider, import { TransactionBlock } from "@mysten/sui.js/transactions";
ObjectId, import { bcs } from "@mysten/sui.js/bcs";
SUI_CLOCK_OBJECT_ID, import { HexString } from "@pythnetwork/price-service-client";
TransactionBlock,
} from "@mysten/sui.js";
import {
HexString,
isAccumulatorUpdateData,
parseAccumulatorUpdateData,
} from "@pythnetwork/price-service-client";
import { Buffer } from "buffer"; import { Buffer } from "buffer";
const MAX_ARGUMENT_SIZE = 16 * 1024; const MAX_ARGUMENT_SIZE = 16 * 1024;
export type ObjectId = string;
export class SuiPythClient { export class SuiPythClient {
private pythPackageId: ObjectId | undefined; private pythPackageId: ObjectId | undefined;
@ -21,7 +15,7 @@ export class SuiPythClient {
private priceFeedObjectIdCache: Map<HexString, ObjectId> = new Map(); private priceFeedObjectIdCache: Map<HexString, ObjectId> = new Map();
private baseUpdateFee: number | undefined; private baseUpdateFee: number | undefined;
constructor( constructor(
public provider: JsonRpcProvider, public provider: SuiClient,
public pythStateId: ObjectId, public pythStateId: ObjectId,
public wormholeStateId: ObjectId public wormholeStateId: ObjectId
) { ) {
@ -41,6 +35,8 @@ export class SuiPythClient {
result.data.content.dataType !== "moveObject" result.data.content.dataType !== "moveObject"
) )
throw new Error("Unable to fetch pyth state object"); throw new Error("Unable to fetch pyth state object");
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this.baseUpdateFee = result.data.content.fields.base_update_fee as number; this.baseUpdateFee = result.data.content.fields.base_update_fee as number;
} }
@ -65,11 +61,14 @@ export class SuiPythClient {
if (result.data?.content?.dataType == "moveObject") { if (result.data?.content?.dataType == "moveObject") {
return result.data.content.fields; return result.data.content.fields;
} }
console.log(result.data?.content);
throw new Error("not move object"); throw new Error(`Cannot fetch package id for object ${objectId}`);
}); });
if ("upgrade_cap" in state) { if ("upgrade_cap" in state) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return state.upgrade_cap.fields.package; return state.upgrade_cap.fields.package;
} }
@ -90,7 +89,7 @@ export class SuiPythClient {
arguments: [ arguments: [
tx.object(this.wormholeStateId), tx.object(this.wormholeStateId),
tx.pure( tx.pure(
builder bcs
.ser("vector<u8>", Array.from(vaa), { .ser("vector<u8>", Array.from(vaa), {
maxSize: MAX_ARGUMENT_SIZE, maxSize: MAX_ARGUMENT_SIZE,
}) })
@ -115,49 +114,31 @@ export class SuiPythClient {
updates: Buffer[], updates: Buffer[],
feedIds: HexString[] feedIds: HexString[]
): Promise<ObjectId[]> { ): Promise<ObjectId[]> {
const wormholePackageId = await this.getWormholePackageId();
const packageId = await this.getPythPackageId(); const packageId = await this.getPythPackageId();
let priceUpdatesHotPotato; let priceUpdatesHotPotato;
if (updates.every((update) => isAccumulatorUpdateData(update))) { if (updates.length > 1) {
if (updates.length > 1) { throw new Error(
throw new Error( "SDK does not support sending multiple accumulator messages in a single transaction"
"SDK does not support sending multiple accumulator messages in a single transaction" );
);
}
const vaa = parseAccumulatorUpdateData(updates[0]).vaa;
const verifiedVaas = await this.verifyVaas([vaa], tx);
[priceUpdatesHotPotato] = tx.moveCall({
target: `${packageId}::pyth::create_authenticated_price_infos_using_accumulator`,
arguments: [
tx.object(this.pythStateId),
tx.pure(
builder
.ser("vector<u8>", Array.from(updates[0]), {
maxSize: MAX_ARGUMENT_SIZE,
})
.toBytes()
),
verifiedVaas[0],
tx.object(SUI_CLOCK_OBJECT_ID),
],
});
} else if (updates.every((vaa) => !isAccumulatorUpdateData(vaa))) {
const verifiedVaas = await this.verifyVaas(updates, tx);
[priceUpdatesHotPotato] = tx.moveCall({
target: `${packageId}::pyth::create_price_infos_hot_potato`,
arguments: [
tx.object(this.pythStateId),
tx.makeMoveVec({
type: `${wormholePackageId}::vaa::VAA`,
objects: verifiedVaas,
}),
tx.object(SUI_CLOCK_OBJECT_ID),
],
});
} else {
throw new Error("Can't mix accumulator and non-accumulator messages");
} }
const vaa = this.extractVaaBytesFromAccumulatorMessage(updates[0]);
const verifiedVaas = await this.verifyVaas([vaa], tx);
[priceUpdatesHotPotato] = tx.moveCall({
target: `${packageId}::pyth::create_authenticated_price_infos_using_accumulator`,
arguments: [
tx.object(this.pythStateId),
tx.pure(
bcs
.ser("vector<u8>", Array.from(updates[0]), {
maxSize: MAX_ARGUMENT_SIZE,
})
.toBytes()
),
verifiedVaas[0],
tx.object(SUI_CLOCK_OBJECT_ID),
],
});
const priceInfoObjects: ObjectId[] = []; const priceInfoObjects: ObjectId[] = [];
const baseUpdateFee = await this.getBaseUpdateFee(); const baseUpdateFee = await this.getBaseUpdateFee();
@ -194,47 +175,29 @@ export class SuiPythClient {
return priceInfoObjects; return priceInfoObjects;
} }
async createPriceFeed(tx: TransactionBlock, updates: Buffer[]) { async createPriceFeed(tx: TransactionBlock, updates: Buffer[]) {
const wormholePackageId = await this.getWormholePackageId();
const packageId = await this.getPythPackageId(); const packageId = await this.getPythPackageId();
if (updates.every((update) => isAccumulatorUpdateData(update))) { if (updates.length > 1) {
if (updates.length > 1) { throw new Error(
throw new Error( "SDK does not support sending multiple accumulator messages in a single transaction"
"SDK does not support sending multiple accumulator messages in a single transaction" );
);
}
const vaa = parseAccumulatorUpdateData(updates[0]).vaa;
const verifiedVaas = await this.verifyVaas([vaa], tx);
tx.moveCall({
target: `${packageId}::pyth::create_price_feeds_using_accumulator`,
arguments: [
tx.object(this.pythStateId),
tx.pure(
builder
.ser("vector<u8>", Array.from(updates[0]), {
maxSize: MAX_ARGUMENT_SIZE,
})
.toBytes()
),
verifiedVaas[0],
tx.object(SUI_CLOCK_OBJECT_ID),
],
});
} else if (updates.every((vaa) => !isAccumulatorUpdateData(vaa))) {
const verifiedVaas = await this.verifyVaas(updates, tx);
tx.moveCall({
target: `${packageId}::pyth::create_price_feeds`,
arguments: [
tx.object(this.pythStateId),
tx.makeMoveVec({
type: `${wormholePackageId}::vaa::VAA`,
objects: verifiedVaas,
}),
tx.object(SUI_CLOCK_OBJECT_ID),
],
});
} else {
throw new Error("Can't mix accumulator and non-accumulator messages");
} }
const vaa = this.extractVaaBytesFromAccumulatorMessage(updates[0]);
const verifiedVaas = await this.verifyVaas([vaa], tx);
tx.moveCall({
target: `${packageId}::pyth::create_price_feeds_using_accumulator`,
arguments: [
tx.object(this.pythStateId),
tx.pure(
bcs
.ser("vector<u8>", Array.from(updates[0]), {
maxSize: MAX_ARGUMENT_SIZE,
})
.toBytes()
),
verifiedVaas[0],
tx.object(SUI_CLOCK_OBJECT_ID),
],
});
} }
/** /**
@ -282,6 +245,8 @@ export class SuiPythClient {
} }
this.priceFeedObjectIdCache.set( this.priceFeedObjectIdCache.set(
normalizedFeedId, normalizedFeedId,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
result.data.content.fields.value result.data.content.fields.value
); );
} }
@ -315,4 +280,22 @@ export class SuiPythClient {
} }
return this.priceTableInfo; return this.priceTableInfo;
} }
/**
* Obtains the vaa bytes embedded in an accumulator message.
* @param accumulatorMessage - the accumulator price update message
* @returns vaa bytes as a uint8 array
*/
extractVaaBytesFromAccumulatorMessage(accumulatorMessage: Buffer): Buffer {
// the first 6 bytes in the accumulator message encode the header, major, and minor bytes
// we ignore them, since we are only interested in the VAA bytes
const trailingPayloadSize = accumulatorMessage.readUint8(6);
const vaaSizeOffset =
7 + // header bytes (header(4) + major(1) + minor(1) + trailing payload size(1))
trailingPayloadSize + // trailing payload (variable number of bytes)
1; // proof_type (1 byte)
const vaaSize = accumulatorMessage.readUint16BE(vaaSizeOffset);
const vaaOffset = vaaSizeOffset + 2;
return accumulatorMessage.subarray(vaaOffset, vaaOffset + vaaSize);
}
} }

View File

@ -1,12 +1,9 @@
import yargs from "yargs"; import yargs from "yargs";
import { hideBin } from "yargs/helpers"; import { hideBin } from "yargs/helpers";
import { import { SuiClient } from "@mysten/sui.js/client";
Connection, import { TransactionBlock } from "@mysten/sui.js/transactions";
Ed25519Keypair, import { Ed25519Keypair } from "@mysten/sui.js/keypairs/ed25519";
JsonRpcProvider,
RawSigner,
TransactionBlock,
} from "@mysten/sui.js";
import { Buffer } from "buffer"; import { Buffer } from "buffer";
import { SuiPythClient } from "../client"; import { SuiPythClient } from "../client";
import { SuiPriceServiceConnection } from "../index"; import { SuiPriceServiceConnection } from "../index";
@ -41,7 +38,7 @@ const argvPromise = yargs(hideBin(process.argv))
}).argv; }).argv;
export function getProvider(url: string) { export function getProvider(url: string) {
return new JsonRpcProvider(new Connection({ fullnode: url })); return new SuiClient({ url });
} }
async function run() { async function run() {
if (process.env.SUI_KEY === undefined) { if (process.env.SUI_KEY === undefined) {
@ -53,29 +50,47 @@ async function run() {
// Fetch the latest price feed update data from the Price Service // Fetch the latest price feed update data from the Price Service
const connection = new SuiPriceServiceConnection(argv["hermes"]); const connection = new SuiPriceServiceConnection(argv["hermes"]);
const feeds = argv["feed-id"] as string[]; const feeds = argv["feed-id"] as string[];
const priceFeedUpdateData = await connection.getPriceFeedsUpdateData(feeds);
const provider = getProvider(argv["full-node"]); const provider = getProvider(argv["full-node"]);
const wormholeStateId = argv["wormhole-state-id"]; const wormholeStateId = argv["wormhole-state-id"];
const pythStateId = argv["pyth-state-id"]; const pythStateId = argv["pyth-state-id"];
const client = new SuiPythClient(provider, pythStateId, wormholeStateId); const client = new SuiPythClient(provider, pythStateId, wormholeStateId);
const newFeeds = [];
const existingFeeds = [];
for (const feed of feeds) {
if ((await client.getPriceFeedObjectId(feed)) == undefined) {
newFeeds.push(feed);
} else {
existingFeeds.push(feed);
}
}
console.log({
newFeeds,
existingFeeds,
});
const tx = new TransactionBlock(); const tx = new TransactionBlock();
await client.updatePriceFeeds(tx, priceFeedUpdateData, feeds); if (existingFeeds.length > 0) {
const updateData = await connection.getPriceFeedsUpdateData(existingFeeds);
await client.updatePriceFeeds(tx, updateData, existingFeeds);
}
if (newFeeds.length > 0) {
const updateData = await connection.getPriceFeedsUpdateData(newFeeds);
await client.createPriceFeed(tx, updateData);
}
const wallet = new RawSigner( const wallet = Ed25519Keypair.fromSecretKey(
Ed25519Keypair.fromSecretKey(Buffer.from(process.env.SUI_KEY, "hex")), Buffer.from(process.env.SUI_KEY, "hex")
provider
); );
const txBlock = { const result = await provider.signAndExecuteTransactionBlock({
signer: wallet,
transactionBlock: tx, transactionBlock: tx,
options: { options: {
showEffects: true, showEffects: true,
showEvents: true, showEvents: true,
}, },
}; });
const result = await wallet.signAndExecuteTransactionBlock(txBlock);
console.dir(result, { depth: null }); console.dir(result, { depth: null });
} }