import { Chain, FuelChain } from "../chains"; import { WormholeContract } from "./wormhole"; import { PYTH_CONTRACT_ABI as FuelContractAbi, FUEL_ETH_ASSET_ID, PriceFeedOutput, DataSourceOutput, } from "@pythnetwork/pyth-fuel-js"; import { Account, Contract, Wallet, arrayify, hexlify, DryRunResult, } from "fuels"; import { PriceFeed, PriceFeedContract, PrivateKey, TxResult } from "../base"; import { TokenQty } from "../token"; import { DataSource } from "@pythnetwork/xc-admin-common"; export class FuelWormholeContract extends WormholeContract { static type = "FuelWormholeContract"; getId(): string { return `${this.chain.getId()}_${this.address}_${FuelWormholeContract.type}`; } getType(): string { return FuelWormholeContract.type; } toJson() { return { chain: this.chain.getId(), address: this.address, type: FuelWormholeContract.type, }; } static fromJson( chain: Chain, parsed: { type: string; address: string; } ): FuelWormholeContract { if (parsed.type !== FuelWormholeContract.type) throw new Error("Invalid type"); if (!(chain instanceof FuelChain)) throw new Error(`Wrong chain type ${chain}`); return new FuelWormholeContract(chain, parsed.address); } constructor(public chain: FuelChain, public address: string) { super(); } async getContract(wallet?: Wallet): Promise { const provider = await this.chain.getProvider(); return new Contract( this.address, FuelContractAbi, wallet ? (wallet as Account) : provider ); } async getCurrentGuardianSetIndex(): Promise { const contract = await this.getContract(); const guardianSetIndex = ( await contract.functions.current_guardian_set_index().get() ).value; return guardianSetIndex; } async getChainId(): Promise { const contract = await this.getContract(); const chainId = (await contract.functions.chain_id().get()).value; return chainId; } async getGuardianSet(): Promise { const contract = await this.getContract(); const guardianSetIndex = await this.getCurrentGuardianSetIndex(); const guardianSet = ( await contract.functions.guardian_set(guardianSetIndex).get() ).value; return guardianSet; } async upgradeGuardianSets( senderPrivateKey: PrivateKey, vaa: Buffer ): Promise { const wallet = await this.chain.getWallet(senderPrivateKey); const contract = await this.getContract(wallet); const tx = await contract.functions .submit_new_guardian_set(arrayify(vaa)) .call(); // you might get `Error updating Guardianset for fuel_testnet_{address} TypeError: response.body.getReader is not a function` but the tx could still be successful, this is due to fuels using native fetch but some other packages in the monorepo is using node-fetch which overrides the fetch here const txResult = await tx.waitForResult(); return { id: tx.transactionId, info: JSON.stringify(txResult.transactionResponse), }; } } export class FuelPriceFeedContract extends PriceFeedContract { static type = "FuelPriceFeedContract"; constructor(public chain: FuelChain, public address: string) { super(); } static fromJson( chain: Chain, parsed: { type: string; address: string } ): FuelPriceFeedContract { if (parsed.type !== FuelPriceFeedContract.type) throw new Error("Invalid type"); if (!(chain instanceof FuelChain)) throw new Error(`Wrong chain type ${chain}`); return new FuelPriceFeedContract(chain, parsed.address); } getId(): string { return `${this.chain.getId()}_${this.address}_${ FuelPriceFeedContract.type }`; } getType(): string { return FuelPriceFeedContract.type; } async getContract(wallet?: Wallet): Promise { const provider = await this.chain.getProvider(); return new Contract( this.address, FuelContractAbi, wallet ? (wallet as Account) : provider ); } async getTotalFee(): Promise { const contract = await this.getContract(); const balance = await contract.getBalance(this.address); return { amount: BigInt(balance.toString()), denom: this.chain.getNativeToken(), }; } async getLastExecutedGovernanceSequence() { const pythContract = await this.getContract(); return Number( (await pythContract.functions.last_executed_governance_sequence().get()) .value ); } async getPriceFeed(feedId: string): Promise { const pythContract = await this.getContract(); const feed = "0x" + feedId; const exists = ( await pythContract.functions.price_feed_exists(hexlify(feed)).get() ).value; if (!exists) { return undefined; } const priceFeed: PriceFeedOutput = ( await pythContract.functions.price_feed_unsafe(feed).get() ).value; return { price: { price: priceFeed.price.price.toString(), conf: priceFeed.price.confidence.toString(), expo: priceFeed.price.exponent.toString(), publishTime: priceFeed.price.publish_time.toString(), }, emaPrice: { price: priceFeed.ema_price.price.toString(), conf: priceFeed.ema_price.confidence.toString(), expo: priceFeed.ema_price.exponent.toString(), publishTime: priceFeed.ema_price.publish_time.toString(), }, }; } async getValidTimePeriod() { const pythContract = await this.getContract(); const validTimePeriod = ( await pythContract.functions.valid_time_period().get() ).value; return Number(validTimePeriod); } /** * Returns the wormhole contract which is being used for VAA verification */ async getWormholeContract(): Promise { // price feed contract and wormhole contract lives on the same address in fuel return new FuelWormholeContract(this.chain, this.address); } async getBaseUpdateFee() { const pythContract = await this.getContract(); const amount = (await pythContract.functions.single_update_fee().get()) .value; return { amount: amount.toString(), denom: this.chain.getNativeToken(), }; } async getDataSources(): Promise { const pythContract = await this.getContract(); const result: DryRunResult = await pythContract.functions.valid_data_sources().get(); return result.value.map( ({ chain_id, emitter_address, }: { chain_id: number; emitter_address: string; }) => { return { emitterChain: chain_id, emitterAddress: emitter_address.replace("0x", ""), }; } ); } async getGovernanceDataSource(): Promise { const pythContract = await this.getContract(); const result: DryRunResult = await pythContract.functions .governance_data_source() .get(); return { emitterChain: result.value.chain_id, emitterAddress: result.value.emitter_address.replace("0x", ""), }; } async executeUpdatePriceFeed(senderPrivateKey: PrivateKey, vaas: Buffer[]) { const wallet = await this.chain.getWallet(senderPrivateKey); const contract = await this.getContract(wallet); const priceFeedUpdateData = vaas.map((vaa) => new Uint8Array(vaa)); const updateFee: number = ( await contract.functions.update_fee(priceFeedUpdateData).get() ).value; const tx = await contract.functions .update_price_feeds(priceFeedUpdateData) .callParams({ forward: [updateFee, hexlify(FUEL_ETH_ASSET_ID)], }) .call(); const txResult = await tx.waitForResult(); return { id: tx.transactionId, info: txResult.transactionResponse }; } async executeGovernanceInstruction( senderPrivateKey: PrivateKey, vaa: Buffer ) { const wallet = await this.chain.getWallet(senderPrivateKey); const contract = await this.getContract(wallet); const tx = await contract.functions .execute_governance_instruction(arrayify(vaa)) .call(); const txResult = await tx.waitForResult(); return { id: tx.transactionId, info: txResult.transactionResponse }; } getChain(): FuelChain { return this.chain; } toJson() { return { chain: this.chain.getId(), address: this.address, type: FuelPriceFeedContract.type, }; } }