| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283 |
- import { PriceFeedContract, PriceFeed, PrivateKey, TxResult } from "../base";
- import { ApiError, BCS, CoinClient, TxnBuilderTypes } from "aptos";
- import { AptosChain, Chain } from "../chains";
- import { DataSource } from "xc_admin_common";
- import { WormholeContract } from "./wormhole";
- type WormholeState = {
- chain_id: { number: string };
- guardian_set_index: { number: string };
- guardian_sets: { handle: string };
- };
- type GuardianSet = {
- guardians: { address: { bytes: string } }[];
- expiration_time: { number: string };
- index: { number: string };
- };
- export class WormholeAptosContract extends WormholeContract {
- constructor(public chain: AptosChain, public address: string) {
- super();
- }
- async getState(): Promise<WormholeState> {
- const client = this.chain.getClient();
- const resources = await client.getAccountResources(this.address);
- const type = "WormholeState";
- for (const resource of resources) {
- if (resource.type === `${this.address}::state::${type}`) {
- return resource.data as WormholeState;
- }
- }
- throw new Error(`${type} resource not found in account ${this.address}`);
- }
- async getCurrentGuardianSetIndex(): Promise<number> {
- const data = await this.getState();
- return Number(data.guardian_set_index.number);
- }
- async getChainId(): Promise<number> {
- const data = await this.getState();
- return Number(data.chain_id.number);
- }
- async getGuardianSet(): Promise<string[]> {
- const data = await this.getState();
- const client = this.chain.getClient();
- const result = (await client.getTableItem(data.guardian_sets.handle, {
- key_type: `u64`,
- value_type: `${this.address}::structs::GuardianSet`,
- key: data.guardian_set_index.number.toString(),
- })) as GuardianSet;
- return result.guardians.map((guardian) => guardian.address.bytes);
- }
- async upgradeGuardianSets(
- senderPrivateKey: PrivateKey,
- vaa: Buffer
- ): Promise<TxResult> {
- const txPayload = new TxnBuilderTypes.TransactionPayloadEntryFunction(
- TxnBuilderTypes.EntryFunction.natural(
- `${this.address}::guardian_set_upgrade`,
- "submit_vaa_entry",
- [],
- [BCS.bcsSerializeBytes(vaa)]
- )
- );
- return this.chain.sendTransaction(senderPrivateKey, txPayload);
- }
- }
- export class AptosPriceFeedContract extends PriceFeedContract {
- static type = "AptosPriceFeedContract";
- /**
- * Given the ids of the pyth state and wormhole state, create a new AptosContract
- * The package ids are derived based on the state ids
- *
- * @param chain the chain which this contract is deployed on
- * @param stateId id of the pyth state for the deployed contract
- * @param wormholeStateId id of the wormhole state for the wormhole contract that pyth binds to
- */
- constructor(
- public chain: AptosChain,
- public stateId: string,
- public wormholeStateId: string
- ) {
- super();
- }
- static fromJson(
- chain: Chain,
- parsed: { type: string; stateId: string; wormholeStateId: string }
- ): AptosPriceFeedContract {
- if (parsed.type !== AptosPriceFeedContract.type)
- throw new Error("Invalid type");
- if (!(chain instanceof AptosChain))
- throw new Error(`Wrong chain type ${chain}`);
- return new AptosPriceFeedContract(
- chain,
- parsed.stateId,
- parsed.wormholeStateId
- );
- }
- async executeGovernanceInstruction(
- senderPrivateKey: PrivateKey,
- vaa: Buffer
- ): Promise<TxResult> {
- const txPayload = new TxnBuilderTypes.TransactionPayloadEntryFunction(
- TxnBuilderTypes.EntryFunction.natural(
- `${this.stateId}::governance`,
- "execute_governance_instruction",
- [],
- [BCS.bcsSerializeBytes(vaa)]
- )
- );
- return this.chain.sendTransaction(senderPrivateKey, txPayload);
- }
- public getWormholeContract(): WormholeAptosContract {
- return new WormholeAptosContract(this.chain, this.wormholeStateId);
- }
- async executeUpdatePriceFeed(
- senderPrivateKey: PrivateKey,
- vaas: Buffer[]
- ): Promise<TxResult> {
- const txPayload = new TxnBuilderTypes.TransactionPayloadEntryFunction(
- TxnBuilderTypes.EntryFunction.natural(
- `${this.stateId}::pyth`,
- "update_price_feeds_with_funder",
- [],
- [BCS.serializeVectorWithFunc(vaas, "serializeBytes")]
- )
- );
- return this.chain.sendTransaction(senderPrivateKey, txPayload);
- }
- getStateResources() {
- const client = this.chain.getClient();
- return client.getAccountResources(this.stateId);
- }
- /**
- * Returns the first occurrence of a resource with the given type in the pyth package state
- * @param type
- */
- async findResource(type: string) {
- const resources = await this.getStateResources();
- for (const resource of resources) {
- if (resource.type === `${this.stateId}::state::${type}`) {
- return resource.data;
- }
- }
- throw new Error(`${type} resource not found in state ${this.stateId}`);
- }
- async getBaseUpdateFee() {
- const data = (await this.findResource("BaseUpdateFee")) as { fee: string };
- return { amount: data.fee };
- }
- getChain(): AptosChain {
- return this.chain;
- }
- private parsePrice(priceInfo: {
- expo: { magnitude: string; negative: boolean };
- price: { magnitude: string; negative: boolean };
- conf: string;
- timestamp: string;
- }) {
- let expo = priceInfo.expo.magnitude;
- if (priceInfo.expo.negative) expo = "-" + expo;
- let price = priceInfo.price.magnitude;
- if (priceInfo.price.negative) price = "-" + price;
- return {
- conf: priceInfo.conf,
- publishTime: priceInfo.timestamp,
- expo,
- price,
- };
- }
- async getPriceFeed(feedId: string): Promise<PriceFeed | undefined> {
- const client = this.chain.getClient();
- const res = (await this.findResource("LatestPriceInfo")) as {
- info: { handle: string };
- };
- const handle = res.info.handle;
- try {
- const priceItemRes = await client.getTableItem(handle, {
- key_type: `${this.stateId}::price_identifier::PriceIdentifier`,
- value_type: `${this.stateId}::price_info::PriceInfo`,
- key: {
- bytes: feedId,
- },
- });
- return {
- price: this.parsePrice(priceItemRes.price_feed.price),
- emaPrice: this.parsePrice(priceItemRes.price_feed.ema_price),
- };
- } catch (e) {
- if (e instanceof ApiError && e.errorCode === "table_item_not_found")
- return undefined;
- throw e;
- }
- }
- async getDataSources(): Promise<DataSource[]> {
- const data = (await this.findResource("DataSources")) as {
- sources: {
- keys: {
- emitter_chain: string;
- emitter_address: { external_address: string };
- }[];
- };
- };
- return data.sources.keys.map((source) => {
- return {
- emitterChain: Number(source.emitter_chain),
- emitterAddress: source.emitter_address.external_address.replace(
- "0x",
- ""
- ),
- };
- });
- }
- async getGovernanceDataSource(): Promise<DataSource> {
- const data = (await this.findResource("GovernanceDataSource")) as {
- source: {
- emitter_chain: string;
- emitter_address: { external_address: string };
- };
- };
- return {
- emitterChain: Number(data.source.emitter_chain),
- emitterAddress: data.source.emitter_address.external_address.replace(
- "0x",
- ""
- ),
- };
- }
- async getLastExecutedGovernanceSequence() {
- const data = (await this.findResource(
- "LastExecutedGovernanceSequence"
- )) as { sequence: string };
- return Number(data.sequence);
- }
- getId(): string {
- return `${this.chain.getId()}_${this.stateId}`;
- }
- getType(): string {
- return AptosPriceFeedContract.type;
- }
- async getTotalFee(): Promise<bigint> {
- const client = new CoinClient(this.chain.getClient());
- return await client.checkBalance(this.stateId);
- }
- async getValidTimePeriod() {
- const data = (await this.findResource("StalePriceThreshold")) as {
- threshold_secs: string;
- };
- return Number(data.threshold_secs);
- }
- toJson() {
- return {
- chain: this.chain.getId(),
- stateId: this.stateId,
- wormholeStateId: this.wormholeStateId,
- type: AptosPriceFeedContract.type,
- };
- }
- }
|