UNPKG

@onelabs/suins

Version:
249 lines (248 loc) 9.91 kB
var __typeError = (msg) => { throw TypeError(msg); }; var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg); var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj)); var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value); var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value); var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method); var _pythPackageId, _wormholePackageId, _priceFeedObjectIdCache, _priceTableInfo, _baseUpdateFee, _SuiPythClient_instances, fetchPriceFeedObjectId_fn, fetchPriceTableInfo_fn, fetchWormholePackageId_fn, fetchPythPackageId_fn, getPackageId_fn, fetchBaseUpdateFee_fn; import { bcs } from "@onelabs/sui/bcs"; import { coinWithBalance } from "@onelabs/sui/transactions"; import { fromBase64, fromHex, parseStructTag } from "@onelabs/sui/utils"; import { PriceServiceConnection } from "./PriceServiceConnection.js"; import { extractVaaBytesFromAccumulatorMessage } from "./pyth-helpers.js"; const MAX_ARGUMENT_SIZE = 16 * 1024; class SuiPriceServiceConnection extends PriceServiceConnection { /** * Fetch price feed update data. * * @param priceIds Array of hex-encoded price IDs. * @returns Array of buffers containing the price update data. */ async getPriceFeedsUpdateData(priceIds) { const latestVaas = await this.getLatestVaas(priceIds); return latestVaas.map((vaa) => fromBase64(vaa)); } } class SuiPythClient { constructor(provider, pythStateId, wormholeStateId) { __privateAdd(this, _SuiPythClient_instances); __privateAdd(this, _pythPackageId); __privateAdd(this, _wormholePackageId); __privateAdd(this, _priceFeedObjectIdCache, /* @__PURE__ */ new Map()); __privateAdd(this, _priceTableInfo); __privateAdd(this, _baseUpdateFee); this.provider = provider; this.pythStateId = pythStateId; this.wormholeStateId = wormholeStateId; } /** * Verifies the VAAs using the Wormhole contract. * * @param vaas Array of VAA buffers to verify. * @param tx Transaction block to add commands to. * @returns Array of verified VAAs. */ async verifyVaas(vaas, tx) { const wormholePackageId = await this.getWormholePackageId(); const verifiedVaas = []; for (const vaa of vaas) { const [verifiedVaa] = tx.moveCall({ target: `${wormholePackageId}::vaa::parse_and_verify`, arguments: [tx.object(this.wormholeStateId), tx.pure.vector("u8", vaa), tx.object.clock()] }); verifiedVaas.push(verifiedVaa); } return verifiedVaas; } /** * Adds the necessary commands for updating the Pyth price feeds to the transaction block. * * @param tx Transaction block to add commands to. * @param updates Array of price feed updates received from the price service. * @param feedIds Array of feed IDs to update (in hex format). */ async updatePriceFeeds(tx, updates, feedIds) { const packageId = await this.getPythPackageId(); let priceUpdatesHotPotato; if (updates.length > 1) { throw new Error( "SDK does not support sending multiple accumulator messages in a single transaction" ); } const vaa = 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.vector(bcs.U8).serialize(Array.from(updates[0]), { maxSize: MAX_ARGUMENT_SIZE }).toBytes() ), verifiedVaas[0], tx.object.clock() ] }); const priceInfoObjects = []; const baseUpdateFee = await this.getBaseUpdateFee(); for (const feedId of feedIds) { const priceInfoObjectId = await this.getPriceFeedObjectId(feedId); if (!priceInfoObjectId) { throw new Error(`Price feed ${feedId} not found, please create it first`); } priceInfoObjects.push(priceInfoObjectId); [priceUpdatesHotPotato] = tx.moveCall({ target: `${packageId}::pyth::update_single_price_feed`, arguments: [ tx.object(this.pythStateId), priceUpdatesHotPotato, tx.object(priceInfoObjectId), coinWithBalance({ balance: baseUpdateFee }), tx.object.clock() ] }); } tx.moveCall({ target: `${packageId}::hot_potato_vector::destroy`, arguments: [priceUpdatesHotPotato], typeArguments: [`${packageId}::price_info::PriceInfo`] }); return priceInfoObjects; } /** * Get the price feed object ID for a given feed ID, caching the promise. * @param feedId */ getPriceFeedObjectId(feedId) { if (!__privateGet(this, _priceFeedObjectIdCache).has(feedId)) { __privateGet(this, _priceFeedObjectIdCache).set( feedId, __privateMethod(this, _SuiPythClient_instances, fetchPriceFeedObjectId_fn).call(this, feedId).catch((err) => { __privateGet(this, _priceFeedObjectIdCache).delete(feedId); throw err; }) ); } return __privateGet(this, _priceFeedObjectIdCache).get(feedId); } /** * Fetches the price table object ID for the current state ID, caching the promise. * @returns Price table object ID and field type */ getPriceTableInfo() { if (!__privateGet(this, _priceTableInfo)) { const promise = __privateMethod(this, _SuiPythClient_instances, fetchPriceTableInfo_fn).call(this).catch((err) => { __privateSet(this, _priceTableInfo, void 0); throw err; }); __privateSet(this, _priceTableInfo, promise); } return __privateGet(this, _priceTableInfo); } /** * Fetches the package ID for the Wormhole contract, with caching. */ getWormholePackageId() { if (!__privateGet(this, _wormholePackageId)) { __privateSet(this, _wormholePackageId, __privateMethod(this, _SuiPythClient_instances, fetchWormholePackageId_fn).call(this)); } return __privateGet(this, _wormholePackageId); } /** * Fetches the package ID for the Pyth contract, with caching. */ getPythPackageId() { if (!__privateGet(this, _pythPackageId)) { __privateSet(this, _pythPackageId, __privateMethod(this, _SuiPythClient_instances, fetchPythPackageId_fn).call(this)); } return __privateGet(this, _pythPackageId); } /** * Returns the cached base update fee, fetching it if necessary. */ getBaseUpdateFee() { if (!__privateGet(this, _baseUpdateFee)) { __privateSet(this, _baseUpdateFee, __privateMethod(this, _SuiPythClient_instances, fetchBaseUpdateFee_fn).call(this)); } return __privateGet(this, _baseUpdateFee); } } _pythPackageId = new WeakMap(); _wormholePackageId = new WeakMap(); _priceFeedObjectIdCache = new WeakMap(); _priceTableInfo = new WeakMap(); _baseUpdateFee = new WeakMap(); _SuiPythClient_instances = new WeakSet(); fetchPriceFeedObjectId_fn = async function(feedId) { const { id: tableId, fieldType } = await this.getPriceTableInfo(); const result = await this.provider.getDynamicFieldObject({ parentId: tableId, name: { type: `${fieldType}::price_identifier::PriceIdentifier`, value: { bytes: Array.from(fromHex(feedId)) } } }); if (!result.data || !result.data.content) { throw new Error(`Price feed object ID for feed ID ${feedId} not found.`); } if (result.data.content.dataType !== "moveObject") { throw new Error("Price feed type mismatch"); } return result.data.content.fields.value; }; fetchPriceTableInfo_fn = async function() { const result = await this.provider.getDynamicFieldObject({ parentId: this.pythStateId, name: { type: "vector<u8>", value: "price_info" } }); if (!result.data || !result.data.type) { throw new Error("Price Table not found, contract may not be initialized"); } const priceIdentifier = parseStructTag(result.data.type).typeParams[0]; if (typeof priceIdentifier === "object" && priceIdentifier !== null && priceIdentifier.name === "PriceIdentifier" && "address" in priceIdentifier) { return { id: result.data.objectId, fieldType: priceIdentifier.address }; } else { throw new Error("fieldType not found"); } }; fetchWormholePackageId_fn = async function() { return await __privateMethod(this, _SuiPythClient_instances, getPackageId_fn).call(this, this.wormholeStateId); }; fetchPythPackageId_fn = async function() { return await __privateMethod(this, _SuiPythClient_instances, getPackageId_fn).call(this, this.pythStateId); }; getPackageId_fn = async function(objectId) { const result = await this.provider.getObject({ id: objectId, options: { showContent: true } }); if (result.data?.content?.dataType === "moveObject" && "upgrade_cap" in result.data.content.fields) { const fields = result.data.content.fields; return fields.upgrade_cap.fields.package; } throw new Error(`Cannot fetch package ID for object ${objectId}`); }; fetchBaseUpdateFee_fn = async function() { const result = await this.provider.getObject({ id: this.pythStateId, options: { showContent: true } }); if (!result.data || result.data.content?.dataType !== "moveObject") { throw new Error("Unable to fetch Pyth state object"); } const fields = result.data.content.fields; return fields.base_update_fee; }; export { SuiPriceServiceConnection, SuiPythClient }; //# sourceMappingURL=pyth.js.map