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

View File

@ -20,12 +20,8 @@ import {
InjectiveExecutor,
} from "@pythnetwork/cosmwasm-deploy-tools";
import { Network } from "@injectivelabs/networks";
import {
Connection,
Ed25519Keypair,
JsonRpcProvider,
RawSigner,
} from "@mysten/sui.js";
import { SuiClient } from "@mysten/sui.js/client";
import { Ed25519Keypair } from "@mysten/sui.js/keypairs/ed25519";
export type ChainConfig = Record<string, string> & {
mainnet: boolean;
@ -290,17 +286,15 @@ export class SuiChain extends Chain {
).encode();
}
getProvider(): JsonRpcProvider {
return new JsonRpcProvider(new Connection({ fullnode: this.rpcUrl }));
getProvider(): SuiClient {
return new SuiClient({ url: this.rpcUrl });
}
async getAccountAddress(privateKey: PrivateKey): Promise<string> {
const provider = this.getProvider();
const keypair = Ed25519Keypair.fromSecretKey(
Buffer.from(privateKey, "hex")
);
const wallet = new RawSigner(keypair, provider);
return await wallet.getAddress();
return keypair.toSuiAddress();
}
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 { DataSource } from "xc_admin_common";
import { PriceFeedContract, PrivateKey, TxResult } from "../base";
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 {
static type = "SuiPriceFeedContract";
@ -96,13 +94,6 @@ export class SuiPriceFeedContract extends PriceFeedContract {
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;
if (priceInfo.fields.expo.fields.negative) expo = "-" + expo;
let price = priceInfo.fields.price.fields.magnitude;
@ -135,10 +126,14 @@ export class SuiPriceFeedContract extends PriceFeedContract {
}
return {
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
.ema_price
),
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
),
};
@ -303,21 +298,25 @@ export class SuiPriceFeedContract extends PriceFeedContract {
keypair: Ed25519Keypair
) {
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,
options: {
showEffects: 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() {
const fields = await this.getStateFields();
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return Number(fields.stale_price_threshold);
}
@ -338,6 +337,8 @@ export class SuiPriceFeedContract extends PriceFeedContract {
if (result.data.content.dataType !== "moveObject") {
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(
({
fields,
@ -359,6 +360,8 @@ export class SuiPriceFeedContract extends PriceFeedContract {
async getGovernanceDataSource(): Promise<DataSource> {
const fields = await this.getStateFields();
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const governanceFields = fields.governance_data_source.fields;
const chainId = governanceFields.emitter_chain;
const emitterAddress =
@ -371,11 +374,15 @@ export class SuiPriceFeedContract extends PriceFeedContract {
async getBaseUpdateFee() {
const fields = await this.getStateFields();
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return { amount: fields.base_update_fee };
}
async getLastExecutedGovernanceSequence() {
const fields = await this.getStateFields();
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
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
stateId: "0x1f9310238ee9298fb703c3419030b35b22bb1cc37113e3bb5007c99aec79e5b8"
wormholeStateId: "0xaeab97f96cf9877fee2883315d459552b2b921edc16d7ceac6eab944dd88919c"
type: SuiPriceFeedContract
- chain: sui_testnet
stateId: "0xb3142a723792001caafc601b7c6fe38f09f3684e360b56d8d90fc574e71e75f3"
wormholeStateId: "0xebba4cc4d614f7a7cdbe883acc76d1cc767922bc96778e7b68be0d15fce27c02"
type: SuiPriceFeedContract
- chain: sui_testnet
stateId: "0x2d82612a354f0b7e52809fc2845642911c7190404620cec8688f68808f8800d8"
wormholeStateId: "0xebba4cc4d614f7a7cdbe883acc76d1cc767922bc96778e7b68be0d15fce27c02"

4402
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,18 +1,12 @@
import {
builder,
JsonRpcProvider,
ObjectId,
SUI_CLOCK_OBJECT_ID,
TransactionBlock,
} from "@mysten/sui.js";
import {
HexString,
isAccumulatorUpdateData,
parseAccumulatorUpdateData,
} from "@pythnetwork/price-service-client";
import { SuiClient } from "@mysten/sui.js/client";
import { SUI_CLOCK_OBJECT_ID } from "@mysten/sui.js/utils";
import { TransactionBlock } from "@mysten/sui.js/transactions";
import { bcs } from "@mysten/sui.js/bcs";
import { HexString } from "@pythnetwork/price-service-client";
import { Buffer } from "buffer";
const MAX_ARGUMENT_SIZE = 16 * 1024;
export type ObjectId = string;
export class SuiPythClient {
private pythPackageId: ObjectId | undefined;
@ -21,7 +15,7 @@ export class SuiPythClient {
private priceFeedObjectIdCache: Map<HexString, ObjectId> = new Map();
private baseUpdateFee: number | undefined;
constructor(
public provider: JsonRpcProvider,
public provider: SuiClient,
public pythStateId: ObjectId,
public wormholeStateId: ObjectId
) {
@ -41,6 +35,8 @@ export class SuiPythClient {
result.data.content.dataType !== "moveObject"
)
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;
}
@ -65,11 +61,14 @@ export class SuiPythClient {
if (result.data?.content?.dataType == "moveObject") {
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) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return state.upgrade_cap.fields.package;
}
@ -90,7 +89,7 @@ export class SuiPythClient {
arguments: [
tx.object(this.wormholeStateId),
tx.pure(
builder
bcs
.ser("vector<u8>", Array.from(vaa), {
maxSize: MAX_ARGUMENT_SIZE,
})
@ -115,24 +114,22 @@ export class SuiPythClient {
updates: Buffer[],
feedIds: HexString[]
): Promise<ObjectId[]> {
const wormholePackageId = await this.getWormholePackageId();
const packageId = await this.getPythPackageId();
let priceUpdatesHotPotato;
if (updates.every((update) => isAccumulatorUpdateData(update))) {
if (updates.length > 1) {
throw new Error(
"SDK does not support sending multiple accumulator messages in a single transaction"
);
}
const vaa = parseAccumulatorUpdateData(updates[0]).vaa;
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(
builder
bcs
.ser("vector<u8>", Array.from(updates[0]), {
maxSize: MAX_ARGUMENT_SIZE,
})
@ -142,22 +139,6 @@ export class SuiPythClient {
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 priceInfoObjects: ObjectId[] = [];
const baseUpdateFee = await this.getBaseUpdateFee();
@ -194,22 +175,20 @@ export class SuiPythClient {
return priceInfoObjects;
}
async createPriceFeed(tx: TransactionBlock, updates: Buffer[]) {
const wormholePackageId = await this.getWormholePackageId();
const packageId = await this.getPythPackageId();
if (updates.every((update) => isAccumulatorUpdateData(update))) {
if (updates.length > 1) {
throw new Error(
"SDK does not support sending multiple accumulator messages in a single transaction"
);
}
const vaa = parseAccumulatorUpdateData(updates[0]).vaa;
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(
builder
bcs
.ser("vector<u8>", Array.from(updates[0]), {
maxSize: MAX_ARGUMENT_SIZE,
})
@ -219,22 +198,6 @@ export class SuiPythClient {
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");
}
}
/**
@ -282,6 +245,8 @@ export class SuiPythClient {
}
this.priceFeedObjectIdCache.set(
normalizedFeedId,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
result.data.content.fields.value
);
}
@ -315,4 +280,22 @@ export class SuiPythClient {
}
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 { hideBin } from "yargs/helpers";
import {
Connection,
Ed25519Keypair,
JsonRpcProvider,
RawSigner,
TransactionBlock,
} from "@mysten/sui.js";
import { SuiClient } from "@mysten/sui.js/client";
import { TransactionBlock } from "@mysten/sui.js/transactions";
import { Ed25519Keypair } from "@mysten/sui.js/keypairs/ed25519";
import { Buffer } from "buffer";
import { SuiPythClient } from "../client";
import { SuiPriceServiceConnection } from "../index";
@ -41,7 +38,7 @@ const argvPromise = yargs(hideBin(process.argv))
}).argv;
export function getProvider(url: string) {
return new JsonRpcProvider(new Connection({ fullnode: url }));
return new SuiClient({ url });
}
async function run() {
if (process.env.SUI_KEY === undefined) {
@ -53,29 +50,47 @@ async function run() {
// Fetch the latest price feed update data from the Price Service
const connection = new SuiPriceServiceConnection(argv["hermes"]);
const feeds = argv["feed-id"] as string[];
const priceFeedUpdateData = await connection.getPriceFeedsUpdateData(feeds);
const provider = getProvider(argv["full-node"]);
const wormholeStateId = argv["wormhole-state-id"];
const pythStateId = argv["pyth-state-id"];
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();
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(
Ed25519Keypair.fromSecretKey(Buffer.from(process.env.SUI_KEY, "hex")),
provider
const wallet = Ed25519Keypair.fromSecretKey(
Buffer.from(process.env.SUI_KEY, "hex")
);
const txBlock = {
const result = await provider.signAndExecuteTransactionBlock({
signer: wallet,
transactionBlock: tx,
options: {
showEffects: true,
showEvents: true,
},
};
const result = await wallet.signAndExecuteTransactionBlock(txBlock);
});
console.dir(result, { depth: null });
}