[contract-manager] Evm upgrades (#971)
* Add evm contract infos + minor improvements * Throw error if chainId is invalid instead of using 0 silently * Add wormholeChainName property for chains to use in governance actions * Return all signatures upon proposal execution * Unify privateKey schema for contract manager * Implement getLastExecutedGovernanceSequence for all contract types * Unify getPriceFeed interface between contracts * Add update price feed for evm * Adjust gasPrice on evm * Add setter for datasources + add global chain for universal governance instructions * Add utility function to execute arbitrary governance Vaa * Remove redundant contract * Better error message when gas is not enough on deployment * Add aptos mainnet config * Restore sui override of setFee governance message generation * Export executeVaa function * Address PR feedbacks * More documentation and cleanup * Remove INFURA_KEY logic and replace RPC endpoints with public ones * EVMContract -> EvmContract, EVMChain -> EvmChain * Make executeUpdatePriceFeed interface on cosmos similar to evm * Better error message and more comment regarding gas
This commit is contained in:
parent
db6bba9526
commit
31ad2b66a3
|
@ -1,7 +1,7 @@
|
|||
import yargs from "yargs";
|
||||
import { hideBin } from "yargs/helpers";
|
||||
import { CosmWasmChain } from "../src/chains";
|
||||
import { CosmWasmContract } from "../src/cosmwasm";
|
||||
import { CosmWasmContract } from "../src/contracts/cosmwasm";
|
||||
import { DefaultStore } from "../src/store";
|
||||
|
||||
const parser = yargs(hideBin(process.argv))
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
"url": "git+https://github.com/pyth-network/pyth-crosschain.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mysten/sui.js": "^0.37.1",
|
||||
"@certusone/wormhole-sdk": "^0.9.8",
|
||||
"@pythnetwork/cosmwasm-deploy-tools": "*",
|
||||
"@pythnetwork/price-service-client": "*",
|
||||
|
|
|
@ -20,6 +20,18 @@ export abstract class Storable {
|
|||
abstract toJson(): any;
|
||||
}
|
||||
|
||||
export interface Price {
|
||||
price: string;
|
||||
conf: string;
|
||||
publishTime: string;
|
||||
expo: string;
|
||||
}
|
||||
|
||||
export interface PriceFeed {
|
||||
price: Price;
|
||||
emaPrice: Price;
|
||||
}
|
||||
|
||||
export abstract class Contract extends Storable {
|
||||
/**
|
||||
* Returns the time period in seconds that stale data is considered valid for.
|
||||
|
@ -42,12 +54,30 @@ export abstract class Contract extends Storable {
|
|||
*/
|
||||
abstract getBaseUpdateFee(): Promise<{ amount: string; denom?: string }>;
|
||||
|
||||
/**
|
||||
* Returns the last governance sequence that was executed on this contract
|
||||
* this number increases based on the sequence number of the governance messages
|
||||
* that are executed on this contract
|
||||
*
|
||||
* This is used to determine which governance messages are stale and can not be executed
|
||||
*/
|
||||
abstract getLastExecutedGovernanceSequence(): Promise<number>;
|
||||
|
||||
/**
|
||||
* Returns the price feed for the given feed id or undefined if not found
|
||||
* @param feedId hex encoded feed id without 0x prefix
|
||||
*/
|
||||
abstract getPriceFeed(feedId: string): Promise<PriceFeed | undefined>;
|
||||
|
||||
/**
|
||||
* Executes the governance instruction contained in the VAA using the sender credentials
|
||||
* @param sender based on the contract type, this can be a private key, a mnemonic, a wallet, etc.
|
||||
* @param senderPrivateKey private key of the sender in hex format without 0x prefix
|
||||
* @param vaa the VAA to execute
|
||||
*/
|
||||
abstract executeGovernanceInstruction(sender: any, vaa: Buffer): Promise<any>;
|
||||
abstract executeGovernanceInstruction(
|
||||
senderPrivateKey: string,
|
||||
vaa: Buffer
|
||||
): Promise<any>;
|
||||
|
||||
/**
|
||||
* Returns the single data source that this contract accepts governance messages from
|
||||
|
|
|
@ -7,18 +7,43 @@ import {
|
|||
EvmUpgradeContract,
|
||||
SuiAuthorizeUpgradeContract,
|
||||
AptosAuthorizeUpgradeContract,
|
||||
toChainId,
|
||||
SetDataSources,
|
||||
DataSource,
|
||||
} from "xc_admin_common";
|
||||
import { AptosClient } from "aptos";
|
||||
import Web3 from "web3";
|
||||
|
||||
export abstract class Chain extends Storable {
|
||||
protected constructor(public id: string) {
|
||||
public wormholeChainName: ChainName;
|
||||
|
||||
/**
|
||||
* Creates a new Chain object
|
||||
* @param id unique id representing this chain
|
||||
* @param mainnet whether this chain is mainnet or testnet/devnet
|
||||
* @param wormholeChainName the name of the wormhole chain that this chain is associated with.
|
||||
* Note that pyth has included additional chain names and ids to the wormhole spec.
|
||||
* @protected
|
||||
*/
|
||||
protected constructor(
|
||||
protected id: string,
|
||||
protected mainnet: boolean,
|
||||
wormholeChainName: string
|
||||
) {
|
||||
super();
|
||||
this.wormholeChainName = wormholeChainName as ChainName;
|
||||
if (toChainId(this.wormholeChainName) === undefined)
|
||||
throw new Error(`Invalid chain name ${wormholeChainName}`);
|
||||
}
|
||||
|
||||
getId(): string {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
isMainnet(): boolean {
|
||||
return this.mainnet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the payload for a governance SetFee instruction for contracts deployed on this chain
|
||||
* @param fee the new fee to set
|
||||
|
@ -26,12 +51,20 @@ export abstract class Chain extends Storable {
|
|||
*/
|
||||
generateGovernanceSetFeePayload(fee: number, exponent: number): Buffer {
|
||||
return new SetFee(
|
||||
this.getId() as ChainName,
|
||||
this.wormholeChainName,
|
||||
BigInt(fee),
|
||||
BigInt(exponent)
|
||||
).encode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the payload for a governance SetDataSources instruction for contracts deployed on this chain
|
||||
* @param datasources the new datasources
|
||||
*/
|
||||
generateGovernanceSetDataSources(datasources: DataSource[]): Buffer {
|
||||
return new SetDataSources(this.wormholeChainName, datasources).encode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the payload for a governance contract upgrade instruction for contracts deployed on this chain
|
||||
* @param upgradeInfo based on the contract type, this can be a contract address, codeId, package digest, etc.
|
||||
|
@ -39,24 +72,53 @@ export abstract class Chain extends Storable {
|
|||
abstract generateGovernanceUpgradePayload(upgradeInfo: any): Buffer;
|
||||
}
|
||||
|
||||
export class GlobalChain extends Chain {
|
||||
static type: string = "GlobalChain";
|
||||
constructor() {
|
||||
super("global", true, "unset");
|
||||
}
|
||||
generateGovernanceUpgradePayload(upgradeInfo: any): Buffer {
|
||||
throw new Error(
|
||||
"Can not create a governance message for upgrading contracts on all chains!"
|
||||
);
|
||||
}
|
||||
|
||||
getType(): string {
|
||||
return GlobalChain.type;
|
||||
}
|
||||
|
||||
toJson(): any {
|
||||
return {
|
||||
id: this.id,
|
||||
wormholeChainName: this.wormholeChainName,
|
||||
mainnet: this.mainnet,
|
||||
type: GlobalChain.type,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class CosmWasmChain extends Chain {
|
||||
static type: string = "CosmWasmChain";
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
mainnet: boolean,
|
||||
wormholeChainName: string,
|
||||
public querierEndpoint: string,
|
||||
public executorEndpoint: string,
|
||||
public gasPrice: string,
|
||||
public prefix: string,
|
||||
public feeDenom: string
|
||||
) {
|
||||
super(id);
|
||||
super(id, mainnet, wormholeChainName);
|
||||
}
|
||||
|
||||
static fromJson(parsed: any): CosmWasmChain {
|
||||
if (parsed.type !== CosmWasmChain.type) throw new Error("Invalid type");
|
||||
return new CosmWasmChain(
|
||||
parsed.id,
|
||||
parsed.mainnet,
|
||||
parsed.wormholeChainName,
|
||||
parsed.querierEndpoint,
|
||||
parsed.executorEndpoint,
|
||||
parsed.gasPrice,
|
||||
|
@ -70,6 +132,8 @@ export class CosmWasmChain extends Chain {
|
|||
querierEndpoint: this.querierEndpoint,
|
||||
executorEndpoint: this.executorEndpoint,
|
||||
id: this.id,
|
||||
wormholeChainName: this.wormholeChainName,
|
||||
mainnet: this.mainnet,
|
||||
gasPrice: this.gasPrice,
|
||||
prefix: this.prefix,
|
||||
feeDenom: this.feeDenom,
|
||||
|
@ -82,28 +146,37 @@ export class CosmWasmChain extends Chain {
|
|||
}
|
||||
|
||||
generateGovernanceUpgradePayload(codeId: bigint): Buffer {
|
||||
return new CosmosUpgradeContract(
|
||||
this.getId() as ChainName,
|
||||
codeId
|
||||
).encode();
|
||||
return new CosmosUpgradeContract(this.wormholeChainName, codeId).encode();
|
||||
}
|
||||
}
|
||||
|
||||
export class SuiChain extends Chain {
|
||||
static type: string = "SuiChain";
|
||||
|
||||
constructor(id: string, public rpcUrl: string) {
|
||||
super(id);
|
||||
constructor(
|
||||
id: string,
|
||||
mainnet: boolean,
|
||||
wormholeChainName: string,
|
||||
public rpcUrl: string
|
||||
) {
|
||||
super(id, mainnet, wormholeChainName);
|
||||
}
|
||||
|
||||
static fromJson(parsed: any): SuiChain {
|
||||
if (parsed.type !== SuiChain.type) throw new Error("Invalid type");
|
||||
return new SuiChain(parsed.id, parsed.rpcUrl);
|
||||
return new SuiChain(
|
||||
parsed.id,
|
||||
parsed.mainnet,
|
||||
parsed.wormholeChainName,
|
||||
parsed.rpcUrl
|
||||
);
|
||||
}
|
||||
|
||||
toJson(): any {
|
||||
return {
|
||||
id: this.id,
|
||||
wormholeChainName: this.wormholeChainName,
|
||||
mainnet: this.mainnet,
|
||||
rpcUrl: this.rpcUrl,
|
||||
type: SuiChain.type,
|
||||
};
|
||||
|
@ -139,30 +212,57 @@ export class SuiChain extends Chain {
|
|||
* @param digest hex string of the 32 byte digest for the new package without the 0x prefix
|
||||
*/
|
||||
generateGovernanceUpgradePayload(digest: string): Buffer {
|
||||
let setFee = new SuiAuthorizeUpgradeContract("sui", digest).encode();
|
||||
return this.wrapWithWormholeGovernancePayload(0, setFee);
|
||||
const upgrade = new SuiAuthorizeUpgradeContract(
|
||||
this.wormholeChainName,
|
||||
digest
|
||||
).encode();
|
||||
return this.wrapWithWormholeGovernancePayload(0, upgrade);
|
||||
}
|
||||
|
||||
generateGovernanceSetFeePayload(fee: number, exponent: number): Buffer {
|
||||
let setFee = new SetFee(
|
||||
"sui", // should always be sui no matter devnet or testnet or mainnet
|
||||
const setFee = new SetFee(
|
||||
this.wormholeChainName,
|
||||
BigInt(fee),
|
||||
BigInt(exponent)
|
||||
).encode();
|
||||
return this.wrapWithWormholeGovernancePayload(3, setFee);
|
||||
}
|
||||
|
||||
generateGovernanceSetDataSources(datasources: DataSource[]): Buffer {
|
||||
const setDataSource = new SetDataSources(
|
||||
this.wormholeChainName,
|
||||
datasources
|
||||
).encode();
|
||||
return this.wrapWithWormholeGovernancePayload(2, setDataSource);
|
||||
}
|
||||
}
|
||||
|
||||
export class EVMChain extends Chain {
|
||||
static type: string = "EVMChain";
|
||||
export class EvmChain extends Chain {
|
||||
static type: string = "EvmChain";
|
||||
|
||||
constructor(id: string, public rpcUrl: string) {
|
||||
super(id);
|
||||
constructor(
|
||||
id: string,
|
||||
mainnet: boolean,
|
||||
wormholeChainName: string,
|
||||
private rpcUrl: string,
|
||||
private networkId: number
|
||||
) {
|
||||
super(id, mainnet, wormholeChainName);
|
||||
}
|
||||
|
||||
static fromJson(parsed: any): EVMChain {
|
||||
if (parsed.type !== EVMChain.type) throw new Error("Invalid type");
|
||||
return new EVMChain(parsed.id, parsed.rpcUrl);
|
||||
static fromJson(parsed: any): EvmChain {
|
||||
if (parsed.type !== EvmChain.type) throw new Error("Invalid type");
|
||||
return new EvmChain(
|
||||
parsed.id,
|
||||
parsed.mainnet,
|
||||
parsed.wormholeChainName,
|
||||
parsed.rpcUrl,
|
||||
parsed.networkId
|
||||
);
|
||||
}
|
||||
|
||||
getRpcUrl(): string {
|
||||
return this.rpcUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -170,27 +270,45 @@ export class EVMChain extends Chain {
|
|||
* @param address hex string of the 20 byte address of the contract to upgrade to without the 0x prefix
|
||||
*/
|
||||
generateGovernanceUpgradePayload(address: string): Buffer {
|
||||
return new EvmUpgradeContract(this.getId() as ChainName, address).encode();
|
||||
return new EvmUpgradeContract(this.wormholeChainName, address).encode();
|
||||
}
|
||||
|
||||
toJson(): any {
|
||||
return {
|
||||
id: this.id,
|
||||
wormholeChainName: this.wormholeChainName,
|
||||
mainnet: this.mainnet,
|
||||
rpcUrl: this.rpcUrl,
|
||||
type: EVMChain.type,
|
||||
networkId: this.networkId,
|
||||
type: EvmChain.type,
|
||||
};
|
||||
}
|
||||
|
||||
getType(): string {
|
||||
return EVMChain.type;
|
||||
return EvmChain.type;
|
||||
}
|
||||
|
||||
async getGasPrice() {
|
||||
const web3 = new Web3(this.getRpcUrl());
|
||||
let gasPrice = await web3.eth.getGasPrice();
|
||||
// some testnets have inaccuarte gas prices that leads to transactions not being mined, we double it since it's free!
|
||||
if (!this.isMainnet()) {
|
||||
gasPrice = (BigInt(gasPrice) * 2n).toString();
|
||||
}
|
||||
return gasPrice;
|
||||
}
|
||||
}
|
||||
|
||||
export class AptosChain extends Chain {
|
||||
static type = "AptosChain";
|
||||
|
||||
constructor(id: string, public rpcUrl: string) {
|
||||
super(id);
|
||||
constructor(
|
||||
id: string,
|
||||
mainnet: boolean,
|
||||
wormholeChainName: string,
|
||||
public rpcUrl: string
|
||||
) {
|
||||
super(id, mainnet, wormholeChainName);
|
||||
}
|
||||
|
||||
getClient(): AptosClient {
|
||||
|
@ -202,14 +320,9 @@ export class AptosChain extends Chain {
|
|||
* @param digest hex string of the 32 byte digest for the new package without the 0x prefix
|
||||
*/
|
||||
generateGovernanceUpgradePayload(digest: string): Buffer {
|
||||
return new AptosAuthorizeUpgradeContract("aptos", digest).encode();
|
||||
}
|
||||
|
||||
generateGovernanceSetFeePayload(fee: number, exponent: number): Buffer {
|
||||
return new SetFee(
|
||||
"aptos", // should always be aptos no matter devnet or testnet or mainnet
|
||||
BigInt(fee),
|
||||
BigInt(exponent)
|
||||
return new AptosAuthorizeUpgradeContract(
|
||||
this.wormholeChainName,
|
||||
digest
|
||||
).encode();
|
||||
}
|
||||
|
||||
|
@ -220,6 +333,8 @@ export class AptosChain extends Chain {
|
|||
toJson(): any {
|
||||
return {
|
||||
id: this.id,
|
||||
wormholeChainName: this.wormholeChainName,
|
||||
mainnet: this.mainnet,
|
||||
rpcUrl: this.rpcUrl,
|
||||
type: AptosChain.type,
|
||||
};
|
||||
|
@ -227,6 +342,11 @@ export class AptosChain extends Chain {
|
|||
|
||||
static fromJson(parsed: any): AptosChain {
|
||||
if (parsed.type !== AptosChain.type) throw new Error("Invalid type");
|
||||
return new AptosChain(parsed.id, parsed.rpcUrl);
|
||||
return new AptosChain(
|
||||
parsed.id,
|
||||
parsed.mainnet,
|
||||
parsed.wormholeChainName,
|
||||
parsed.rpcUrl
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Contract } from "./base";
|
||||
import { AptosChain, Chain } from "./chains";
|
||||
import { Contract, PriceFeed } from "../base";
|
||||
import { AptosChain, Chain } from "../chains";
|
||||
import { DataSource } from "xc_admin_common";
|
||||
|
||||
export class AptosContract extends Contract {
|
||||
|
@ -28,7 +28,10 @@ export class AptosContract extends Contract {
|
|||
return new AptosContract(chain, parsed.stateId, parsed.wormholeStateId);
|
||||
}
|
||||
|
||||
executeGovernanceInstruction(sender: any, vaa: Buffer): Promise<any> {
|
||||
executeGovernanceInstruction(
|
||||
senderPrivateKey: string,
|
||||
vaa: Buffer
|
||||
): Promise<any> {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
|
||||
|
@ -60,6 +63,41 @@ export class AptosContract extends Contract {
|
|||
return this.chain;
|
||||
}
|
||||
|
||||
private parsePrice(priceInfo: any) {
|
||||
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 any;
|
||||
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: any) {
|
||||
if (e.errorCode === "table_item_not_found") return undefined;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
async getDataSources(): Promise<DataSource[]> {
|
||||
const data = (await this.findResource("DataSources")) as any;
|
||||
return data.sources.keys.map((source: any) => {
|
||||
|
@ -84,6 +122,13 @@ export class AptosContract extends Contract {
|
|||
};
|
||||
}
|
||||
|
||||
async getLastExecutedGovernanceSequence() {
|
||||
const data = (await this.findResource(
|
||||
"LastExecutedGovernanceSequence"
|
||||
)) as any;
|
||||
return Number(data.sequence);
|
||||
}
|
||||
|
||||
getId(): string {
|
||||
return `${this.chain.getId()}_${this.stateId}`;
|
||||
}
|
||||
|
@ -99,7 +144,7 @@ export class AptosContract extends Contract {
|
|||
|
||||
toJson() {
|
||||
return {
|
||||
chain: this.chain.id,
|
||||
chain: this.chain.getId(),
|
||||
stateId: this.stateId,
|
||||
wormholeStateId: this.wormholeStateId,
|
||||
type: AptosContract.type,
|
|
@ -1,20 +1,21 @@
|
|||
import { Chain, CosmWasmChain } from "./chains";
|
||||
import { Chain, CosmWasmChain } from "../chains";
|
||||
import { readFileSync } from "fs";
|
||||
import { getPythConfig } from "@pythnetwork/cosmwasm-deploy-tools/lib/configs";
|
||||
import { CHAINS, DataSource } from "xc_admin_common";
|
||||
import { DeploymentType } from "@pythnetwork/cosmwasm-deploy-tools/lib/helper";
|
||||
import {
|
||||
CosmwasmExecutor,
|
||||
Price,
|
||||
PythWrapperExecutor,
|
||||
PythWrapperQuerier,
|
||||
} from "@pythnetwork/cosmwasm-deploy-tools";
|
||||
} from "@pythnetwork/cosmwasm-deploy-tools/lib";
|
||||
import {
|
||||
ContractInfoResponse,
|
||||
CosmwasmQuerier,
|
||||
} from "@pythnetwork/cosmwasm-deploy-tools/lib/chains-manager/chain-querier";
|
||||
import { PriceServiceConnection } from "@pythnetwork/price-service-client";
|
||||
import { CosmWasmClient } from "@cosmjs/cosmwasm-stargate";
|
||||
import { Contract } from "./base";
|
||||
import { Contract } from "../base";
|
||||
|
||||
/**
|
||||
* Variables here need to be snake case to match the on-chain contract configs
|
||||
|
@ -93,16 +94,16 @@ export class CosmWasmContract extends Contract {
|
|||
* Stores the wasm code on the specified chain using the provided mnemonic as the signer
|
||||
* You can find the wasm artifacts from the repo releases
|
||||
* @param chain chain to store the code on
|
||||
* @param mnemonic mnemonic to use for signing the transaction
|
||||
* @param privateKey private key to use for signing the transaction in hex format without 0x prefix
|
||||
* @param wasmPath path in your local filesystem to the wasm artifact
|
||||
*/
|
||||
static async storeCode(
|
||||
chain: CosmWasmChain,
|
||||
mnemonic: string,
|
||||
privateKey: string,
|
||||
wasmPath: string
|
||||
) {
|
||||
const contractBytes = readFileSync(wasmPath);
|
||||
let executor = this.getExecutor(chain, mnemonic);
|
||||
let executor = await this.getExecutor(chain, privateKey);
|
||||
return executor.storeCode({ contractBytes });
|
||||
}
|
||||
|
||||
|
@ -111,15 +112,15 @@ export class CosmWasmContract extends Contract {
|
|||
* @param chain chain to deploy to
|
||||
* @param codeId codeId of the uploaded wasm code. You can get this from the storeCode result
|
||||
* @param config deployment config for initializing the contract (data sources, governance source, etc)
|
||||
* @param mnemonic mnemonic to use for signing the transaction
|
||||
* @param privateKey private key to use for signing the transaction in hex format without 0x prefix
|
||||
*/
|
||||
static async initialize(
|
||||
chain: CosmWasmChain,
|
||||
codeId: number,
|
||||
config: CosmWasmContract.DeploymentConfig,
|
||||
mnemonic: string
|
||||
privateKey: string
|
||||
): Promise<CosmWasmContract> {
|
||||
let executor = this.getExecutor(chain, mnemonic);
|
||||
let executor = await this.getExecutor(chain, privateKey);
|
||||
let result = await executor.instantiateContract({
|
||||
codeId: codeId,
|
||||
instMsg: config,
|
||||
|
@ -140,26 +141,25 @@ export class CosmWasmContract extends Contract {
|
|||
* more control over the deployment process.
|
||||
* @param chain
|
||||
* @param wormholeContract
|
||||
* @param mnemonic
|
||||
* @param privateKey private key to use for signing the transaction in hex format without 0x prefix
|
||||
* @param wasmPath
|
||||
*/
|
||||
static async deploy(
|
||||
chain: CosmWasmChain,
|
||||
wormholeContract: string,
|
||||
mnemonic: string,
|
||||
privateKey: string,
|
||||
wasmPath: string
|
||||
): Promise<CosmWasmContract> {
|
||||
let config = this.getDeploymentConfig(chain, "edge", wormholeContract);
|
||||
const { codeId } = await this.storeCode(chain, mnemonic, wasmPath);
|
||||
return this.initialize(chain, codeId, config, mnemonic);
|
||||
const { codeId } = await this.storeCode(chain, privateKey, wasmPath);
|
||||
return this.initialize(chain, codeId, config, privateKey);
|
||||
}
|
||||
|
||||
private static getExecutor(chain: CosmWasmChain, mnemonic: string) {
|
||||
private static async getExecutor(chain: CosmWasmChain, privateKey: string) {
|
||||
// TODO: logic for injective
|
||||
return new CosmwasmExecutor(
|
||||
chain.executorEndpoint,
|
||||
mnemonic,
|
||||
chain.prefix,
|
||||
await CosmwasmExecutor.getSignerFromPrivateKey(privateKey, chain.prefix),
|
||||
chain.gasPrice + chain.feeDenom
|
||||
);
|
||||
}
|
||||
|
@ -170,7 +170,7 @@ export class CosmWasmContract extends Contract {
|
|||
|
||||
toJson() {
|
||||
return {
|
||||
chain: this.chain.id,
|
||||
chain: this.chain.getId(),
|
||||
address: this.address,
|
||||
type: CosmWasmContract.type,
|
||||
};
|
||||
|
@ -210,12 +210,34 @@ export class CosmWasmContract extends Contract {
|
|||
return config;
|
||||
}
|
||||
|
||||
async getLastExecutedGovernanceSequence() {
|
||||
const config = await this.getConfig();
|
||||
return Number(config.config_v1.governance_sequence_number);
|
||||
}
|
||||
|
||||
// TODO: function for upgrading the contract
|
||||
// TODO: Cleanup and more strict linter to convert let to const
|
||||
|
||||
async getPriceFeed(feedId: string): Promise<any> {
|
||||
private parsePrice(priceInfo: Price) {
|
||||
return {
|
||||
conf: priceInfo.conf.toString(),
|
||||
publishTime: priceInfo.publish_time.toString(),
|
||||
expo: priceInfo.expo.toString(),
|
||||
price: priceInfo.price.toString(),
|
||||
};
|
||||
}
|
||||
|
||||
async getPriceFeed(feedId: string) {
|
||||
let querier = await this.getQuerier();
|
||||
return querier.getPriceFeed(this.address, feedId);
|
||||
try {
|
||||
const response = await querier.getPriceFeed(this.address, feedId);
|
||||
return {
|
||||
price: this.parsePrice(response.price),
|
||||
emaPrice: this.parsePrice(response.ema_price),
|
||||
};
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
equalDataSources(
|
||||
|
@ -271,37 +293,23 @@ export class CosmWasmContract extends Contract {
|
|||
else return "unknown";
|
||||
}
|
||||
|
||||
async executeUpdatePriceFeed(feedId: string, mnemonic: string) {
|
||||
const deploymentType = await this.getDeploymentType();
|
||||
const priceServiceConnection = new PriceServiceConnection(
|
||||
deploymentType === "stable"
|
||||
? "https://xc-mainnet.pyth.network"
|
||||
: "https://xc-testnet.pyth.network"
|
||||
);
|
||||
|
||||
const vaas = await priceServiceConnection.getLatestVaas([feedId]);
|
||||
const fund = await this.getUpdateFee(vaas);
|
||||
let executor = new CosmwasmExecutor(
|
||||
this.chain.executorEndpoint,
|
||||
mnemonic,
|
||||
this.chain.prefix,
|
||||
this.chain.gasPrice + this.chain.feeDenom
|
||||
async executeUpdatePriceFeed(senderPrivateKey: string, vaas: Buffer[]) {
|
||||
const base64Vaas = vaas.map((v) => v.toString("base64"));
|
||||
const fund = await this.getUpdateFee(base64Vaas);
|
||||
let executor = await CosmWasmContract.getExecutor(
|
||||
this.chain,
|
||||
senderPrivateKey
|
||||
);
|
||||
let pythExecutor = new PythWrapperExecutor(executor);
|
||||
return pythExecutor.executeUpdatePriceFeeds({
|
||||
contractAddr: this.address,
|
||||
vaas,
|
||||
vaas: base64Vaas,
|
||||
fund,
|
||||
});
|
||||
}
|
||||
|
||||
async executeGovernanceInstruction(mnemonic: string, vaa: Buffer) {
|
||||
let executor = new CosmwasmExecutor(
|
||||
this.chain.executorEndpoint,
|
||||
mnemonic,
|
||||
this.chain.prefix,
|
||||
this.chain.gasPrice + this.chain.feeDenom
|
||||
);
|
||||
async executeGovernanceInstruction(privateKey: string, vaa: Buffer) {
|
||||
let executor = await CosmWasmContract.getExecutor(this.chain, privateKey);
|
||||
let pythExecutor = new PythWrapperExecutor(executor);
|
||||
return pythExecutor.executeGovernanceInstruction({
|
||||
contractAddr: this.address,
|
|
@ -0,0 +1,329 @@
|
|||
import Web3 from "web3"; //TODO: decide on using web3 or ethers.js
|
||||
import PythInterfaceAbi from "@pythnetwork/pyth-sdk-solidity/abis/IPyth.json";
|
||||
import { Contract } from "../base";
|
||||
import { Chain, EvmChain } from "../chains";
|
||||
import { DataSource } from "xc_admin_common";
|
||||
|
||||
// Just to make sure tx gas limit is enough
|
||||
const GAS_ESTIMATE_MULTIPLIER = 2;
|
||||
const EXTENDED_PYTH_ABI = [
|
||||
{
|
||||
inputs: [],
|
||||
name: "governanceDataSource",
|
||||
outputs: [
|
||||
{
|
||||
components: [
|
||||
{
|
||||
internalType: "uint16",
|
||||
name: "chainId",
|
||||
type: "uint16",
|
||||
},
|
||||
{
|
||||
internalType: "bytes32",
|
||||
name: "emitterAddress",
|
||||
type: "bytes32",
|
||||
},
|
||||
],
|
||||
internalType: "struct PythInternalStructs.DataSource",
|
||||
name: "",
|
||||
type: "tuple",
|
||||
},
|
||||
],
|
||||
stateMutability: "view",
|
||||
type: "function",
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: "bytes",
|
||||
name: "encodedVM",
|
||||
type: "bytes",
|
||||
},
|
||||
],
|
||||
name: "executeGovernanceInstruction",
|
||||
outputs: [],
|
||||
stateMutability: "nonpayable",
|
||||
type: "function",
|
||||
},
|
||||
{
|
||||
inputs: [],
|
||||
name: "version",
|
||||
outputs: [{ internalType: "string", name: "", type: "string" }],
|
||||
stateMutability: "pure",
|
||||
type: "function",
|
||||
},
|
||||
{
|
||||
inputs: [],
|
||||
name: "singleUpdateFeeInWei",
|
||||
outputs: [
|
||||
{
|
||||
internalType: "uint256",
|
||||
name: "",
|
||||
type: "uint256",
|
||||
},
|
||||
],
|
||||
stateMutability: "view",
|
||||
type: "function",
|
||||
},
|
||||
{
|
||||
inputs: [],
|
||||
name: "validDataSources",
|
||||
outputs: [
|
||||
{
|
||||
components: [
|
||||
{
|
||||
internalType: "uint16",
|
||||
name: "chainId",
|
||||
type: "uint16",
|
||||
},
|
||||
{
|
||||
internalType: "bytes32",
|
||||
name: "emitterAddress",
|
||||
type: "bytes32",
|
||||
},
|
||||
],
|
||||
internalType: "struct PythInternalStructs.DataSource[]",
|
||||
name: "",
|
||||
type: "tuple[]",
|
||||
},
|
||||
],
|
||||
stateMutability: "view",
|
||||
type: "function",
|
||||
constant: true,
|
||||
},
|
||||
{
|
||||
inputs: [],
|
||||
name: "lastExecutedGovernanceSequence",
|
||||
outputs: [
|
||||
{
|
||||
internalType: "uint64",
|
||||
name: "",
|
||||
type: "uint64",
|
||||
},
|
||||
],
|
||||
stateMutability: "view",
|
||||
type: "function",
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: "bytes32",
|
||||
name: "id",
|
||||
type: "bytes32",
|
||||
},
|
||||
],
|
||||
name: "priceFeedExists",
|
||||
outputs: [
|
||||
{
|
||||
internalType: "bool",
|
||||
name: "",
|
||||
type: "bool",
|
||||
},
|
||||
],
|
||||
stateMutability: "view",
|
||||
type: "function",
|
||||
},
|
||||
...PythInterfaceAbi,
|
||||
] as any;
|
||||
|
||||
export class EvmContract extends Contract {
|
||||
static type = "EvmContract";
|
||||
|
||||
constructor(public chain: EvmChain, public address: string) {
|
||||
super();
|
||||
}
|
||||
|
||||
static fromJson(chain: Chain, parsed: any): EvmContract {
|
||||
if (parsed.type !== EvmContract.type) throw new Error("Invalid type");
|
||||
if (!(chain instanceof EvmChain))
|
||||
throw new Error(`Wrong chain type ${chain}`);
|
||||
return new EvmContract(chain, parsed.address);
|
||||
}
|
||||
|
||||
getId(): string {
|
||||
return `${this.chain.getId()}_${this.address}`;
|
||||
}
|
||||
|
||||
getType(): string {
|
||||
return EvmContract.type;
|
||||
}
|
||||
|
||||
async getVersion(): Promise<string> {
|
||||
const pythContract = this.getContract();
|
||||
const result = await pythContract.methods.version().call();
|
||||
return result;
|
||||
}
|
||||
|
||||
static async deploy(
|
||||
chain: EvmChain,
|
||||
privateKey: string,
|
||||
abi: any,
|
||||
bytecode: string
|
||||
): Promise<EvmContract> {
|
||||
const web3 = new Web3(chain.getRpcUrl());
|
||||
const signer = web3.eth.accounts.privateKeyToAccount(privateKey);
|
||||
web3.eth.accounts.wallet.add(signer);
|
||||
const contract = new web3.eth.Contract(abi);
|
||||
const deployTx = contract.deploy({ data: bytecode });
|
||||
const gas = await deployTx.estimateGas();
|
||||
const gasPrice = await chain.getGasPrice();
|
||||
const deployerBalance = await web3.eth.getBalance(signer.address);
|
||||
const gasDiff = BigInt(gas) * BigInt(gasPrice) - BigInt(deployerBalance);
|
||||
if (gasDiff > 0n) {
|
||||
throw new Error(
|
||||
`Insufficient funds to deploy contract. Need ${gas} (gas) * ${gasPrice} (gasPrice)= ${
|
||||
BigInt(gas) * BigInt(gasPrice)
|
||||
} wei, but only have ${deployerBalance} wei. We need ${
|
||||
Number(gasDiff) / 10 ** 18
|
||||
} ETH more.`
|
||||
);
|
||||
}
|
||||
|
||||
const deployedContract = await deployTx.send({
|
||||
from: signer.address,
|
||||
gas,
|
||||
gasPrice,
|
||||
});
|
||||
return new EvmContract(chain, deployedContract.options.address);
|
||||
}
|
||||
|
||||
getContract() {
|
||||
const web3 = new Web3(this.chain.getRpcUrl());
|
||||
const pythContract = new web3.eth.Contract(EXTENDED_PYTH_ABI, this.address);
|
||||
return pythContract;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bytecode of the contract in hex format
|
||||
*/
|
||||
async getCode(): Promise<string> {
|
||||
const web3 = new Web3(this.chain.getRpcUrl());
|
||||
return web3.eth.getCode(this.address);
|
||||
}
|
||||
|
||||
async getLastExecutedGovernanceSequence() {
|
||||
const pythContract = await this.getContract();
|
||||
return Number(
|
||||
await pythContract.methods.lastExecutedGovernanceSequence().call()
|
||||
);
|
||||
}
|
||||
|
||||
async getPriceFeed(feedId: string) {
|
||||
const pythContract = this.getContract();
|
||||
const feed = "0x" + feedId;
|
||||
const exists = await pythContract.methods.priceFeedExists(feed).call();
|
||||
if (!exists) {
|
||||
return undefined;
|
||||
}
|
||||
const [price, conf, expo, publishTime] = await pythContract.methods
|
||||
.getPriceUnsafe(feed)
|
||||
.call();
|
||||
|
||||
const [emaPrice, emaConf, emaExpo, emaPublishTime] =
|
||||
await pythContract.methods.getEmaPriceUnsafe(feed).call();
|
||||
return {
|
||||
price: { price, conf, expo, publishTime },
|
||||
emaPrice: {
|
||||
price: emaPrice,
|
||||
conf: emaConf,
|
||||
expo: emaExpo,
|
||||
publishTime: emaPublishTime,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async getValidTimePeriod() {
|
||||
const pythContract = this.getContract();
|
||||
const result = await pythContract.methods.getValidTimePeriod().call();
|
||||
return Number(result);
|
||||
}
|
||||
|
||||
async getBaseUpdateFee() {
|
||||
const pythContract = this.getContract();
|
||||
const result = await pythContract.methods.singleUpdateFeeInWei().call();
|
||||
return { amount: result };
|
||||
}
|
||||
|
||||
async getDataSources(): Promise<DataSource[]> {
|
||||
const pythContract = this.getContract();
|
||||
const result = await pythContract.methods.validDataSources().call();
|
||||
return result.map(
|
||||
({
|
||||
chainId,
|
||||
emitterAddress,
|
||||
}: {
|
||||
chainId: string;
|
||||
emitterAddress: string;
|
||||
}) => {
|
||||
return {
|
||||
emitterChain: Number(chainId),
|
||||
emitterAddress: emitterAddress.replace("0x", ""),
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async getGovernanceDataSource(): Promise<DataSource> {
|
||||
const pythContract = this.getContract();
|
||||
const [chainId, emitterAddress] = await pythContract.methods
|
||||
.governanceDataSource()
|
||||
.call();
|
||||
return {
|
||||
emitterChain: Number(chainId),
|
||||
emitterAddress: emitterAddress.replace("0x", ""),
|
||||
};
|
||||
}
|
||||
|
||||
async executeUpdatePriceFeed(senderPrivateKey: string, vaas: Buffer[]) {
|
||||
const web3 = new Web3(this.chain.getRpcUrl());
|
||||
const { address } = web3.eth.accounts.wallet.add(senderPrivateKey);
|
||||
const pythContract = new web3.eth.Contract(EXTENDED_PYTH_ABI, this.address);
|
||||
const priceFeedUpdateData = vaas.map((vaa) => "0x" + vaa.toString("hex"));
|
||||
const updateFee = await pythContract.methods
|
||||
.getUpdateFee(priceFeedUpdateData)
|
||||
.call();
|
||||
const transactionObject =
|
||||
pythContract.methods.updatePriceFeeds(priceFeedUpdateData);
|
||||
const gasEstiamte = await transactionObject.estimateGas({
|
||||
from: address,
|
||||
gas: 100000000,
|
||||
value: updateFee,
|
||||
});
|
||||
return transactionObject.send({
|
||||
from: address,
|
||||
value: updateFee,
|
||||
gas: gasEstiamte * GAS_ESTIMATE_MULTIPLIER,
|
||||
gasPrice: await this.chain.getGasPrice(),
|
||||
});
|
||||
}
|
||||
|
||||
async executeGovernanceInstruction(senderPrivateKey: string, vaa: Buffer) {
|
||||
const web3 = new Web3(this.chain.getRpcUrl());
|
||||
const { address } = web3.eth.accounts.wallet.add(senderPrivateKey);
|
||||
const pythContract = new web3.eth.Contract(EXTENDED_PYTH_ABI, this.address);
|
||||
const transactionObject = pythContract.methods.executeGovernanceInstruction(
|
||||
"0x" + vaa.toString("hex")
|
||||
);
|
||||
const gasEstiamte = await transactionObject.estimateGas({
|
||||
from: address,
|
||||
gas: 100000000,
|
||||
});
|
||||
return transactionObject.send({
|
||||
from: address,
|
||||
gas: gasEstiamte * GAS_ESTIMATE_MULTIPLIER,
|
||||
gasPrice: await this.chain.getGasPrice(),
|
||||
});
|
||||
}
|
||||
|
||||
getChain(): EvmChain {
|
||||
return this.chain;
|
||||
}
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
chain: this.chain.getId(),
|
||||
address: this.address,
|
||||
type: EvmContract.type,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -7,9 +7,9 @@ import {
|
|||
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 { Contract } from "./base";
|
||||
import { Contract } from "../base";
|
||||
|
||||
export class SuiContract extends Contract {
|
||||
static type = "SuiContract";
|
||||
|
@ -47,7 +47,7 @@ export class SuiContract extends Contract {
|
|||
|
||||
toJson() {
|
||||
return {
|
||||
chain: this.chain.id,
|
||||
chain: this.chain.getId(),
|
||||
stateId: this.stateId,
|
||||
wormholeStateId: this.wormholeStateId,
|
||||
type: SuiContract.type,
|
||||
|
@ -112,6 +112,26 @@ export class SuiContract extends Contract {
|
|||
return result.data.objectId;
|
||||
}
|
||||
|
||||
private async parsePrice(priceInfo: any) {
|
||||
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;
|
||||
if (priceInfo.fields.price.fields.negative) price = "-" + price;
|
||||
return {
|
||||
conf: priceInfo.fields.conf,
|
||||
publishTime: priceInfo.fields.timestamp,
|
||||
expo,
|
||||
price,
|
||||
};
|
||||
}
|
||||
|
||||
async getPriceFeed(feedId: string) {
|
||||
const tableId = await this.getPriceTableId();
|
||||
const provider = this.getProvider();
|
||||
|
@ -125,7 +145,7 @@ export class SuiContract extends Contract {
|
|||
},
|
||||
});
|
||||
if (!result.data || !result.data.content) {
|
||||
throw new Error("Price feed not found");
|
||||
return undefined;
|
||||
}
|
||||
if (result.data.content.dataType !== "moveObject") {
|
||||
throw new Error("Price feed type mismatch");
|
||||
|
@ -145,7 +165,15 @@ export class SuiContract extends Contract {
|
|||
`Expected ${priceInfoObjectId} to be a moveObject (PriceInfoObject)`
|
||||
);
|
||||
}
|
||||
return priceInfo.data.content.fields;
|
||||
return {
|
||||
emaPrice: await this.parsePrice(
|
||||
priceInfo.data.content.fields.price_info.fields.price_feed.fields
|
||||
.ema_price
|
||||
),
|
||||
price: await this.parsePrice(
|
||||
priceInfo.data.content.fields.price_info.fields.price_feed.fields.price
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -167,7 +195,10 @@ export class SuiContract extends Contract {
|
|||
return this.executeTransaction(tx, keypair);
|
||||
}
|
||||
|
||||
async executeGovernanceInstruction(keypair: Ed25519Keypair, vaa: Buffer) {
|
||||
async executeGovernanceInstruction(senderPrivateKey: string, vaa: Buffer) {
|
||||
const keypair = Ed25519Keypair.fromSecretKey(
|
||||
Buffer.from(senderPrivateKey, "hex")
|
||||
);
|
||||
const tx = new TransactionBlock();
|
||||
const packageId = await this.getPythPackageId();
|
||||
let decreeReceipt = await this.getVaaDecreeReceipt(tx, packageId, vaa);
|
||||
|
@ -321,6 +352,11 @@ export class SuiContract extends Contract {
|
|||
return { amount: fields.base_update_fee };
|
||||
}
|
||||
|
||||
async getLastExecutedGovernanceSequence() {
|
||||
const fields = await this.getStateFields();
|
||||
return Number(fields.last_executed_governance_sequence);
|
||||
}
|
||||
|
||||
private getProvider() {
|
||||
return new JsonRpcProvider(new Connection({ fullnode: this.chain.rpcUrl }));
|
||||
}
|
|
@ -1,191 +0,0 @@
|
|||
import Web3 from "web3"; //TODO: decide on using web3 or ethers.js
|
||||
import PythInterfaceAbi from "@pythnetwork/pyth-sdk-solidity/abis/IPyth.json";
|
||||
import { Contract } from "./base";
|
||||
import { Chain, EVMChain } from "./chains";
|
||||
import { DataSource } from "xc_admin_common";
|
||||
|
||||
const EXTENDED_PYTH_ABI = [
|
||||
{
|
||||
inputs: [],
|
||||
name: "governanceDataSource",
|
||||
outputs: [
|
||||
{
|
||||
components: [
|
||||
{
|
||||
internalType: "uint16",
|
||||
name: "chainId",
|
||||
type: "uint16",
|
||||
},
|
||||
{
|
||||
internalType: "bytes32",
|
||||
name: "emitterAddress",
|
||||
type: "bytes32",
|
||||
},
|
||||
],
|
||||
internalType: "struct PythInternalStructs.DataSource",
|
||||
name: "",
|
||||
type: "tuple",
|
||||
},
|
||||
],
|
||||
stateMutability: "view",
|
||||
type: "function",
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: "bytes",
|
||||
name: "encodedVM",
|
||||
type: "bytes",
|
||||
},
|
||||
],
|
||||
name: "executeGovernanceInstruction",
|
||||
outputs: [],
|
||||
stateMutability: "nonpayable",
|
||||
type: "function",
|
||||
},
|
||||
{
|
||||
inputs: [],
|
||||
name: "singleUpdateFeeInWei",
|
||||
outputs: [
|
||||
{
|
||||
internalType: "uint256",
|
||||
name: "",
|
||||
type: "uint256",
|
||||
},
|
||||
],
|
||||
stateMutability: "view",
|
||||
type: "function",
|
||||
},
|
||||
{
|
||||
inputs: [],
|
||||
name: "validDataSources",
|
||||
outputs: [
|
||||
{
|
||||
components: [
|
||||
{
|
||||
internalType: "uint16",
|
||||
name: "chainId",
|
||||
type: "uint16",
|
||||
},
|
||||
{
|
||||
internalType: "bytes32",
|
||||
name: "emitterAddress",
|
||||
type: "bytes32",
|
||||
},
|
||||
],
|
||||
internalType: "struct PythInternalStructs.DataSource[]",
|
||||
name: "",
|
||||
type: "tuple[]",
|
||||
},
|
||||
],
|
||||
stateMutability: "view",
|
||||
type: "function",
|
||||
constant: true,
|
||||
},
|
||||
...PythInterfaceAbi,
|
||||
] as any;
|
||||
|
||||
export class EVMContract extends Contract {
|
||||
static type = "EVMContract";
|
||||
|
||||
constructor(public chain: EVMChain, public address: string) {
|
||||
super();
|
||||
}
|
||||
|
||||
static fromJson(chain: Chain, parsed: any): EVMContract {
|
||||
if (parsed.type !== EVMContract.type) throw new Error("Invalid type");
|
||||
if (!(chain instanceof EVMChain))
|
||||
throw new Error(`Wrong chain type ${chain}`);
|
||||
return new EVMContract(chain, parsed.address);
|
||||
}
|
||||
|
||||
getId(): string {
|
||||
return `${this.chain.getId()}_${this.address}`;
|
||||
}
|
||||
|
||||
getType(): string {
|
||||
return EVMContract.type;
|
||||
}
|
||||
|
||||
getContract() {
|
||||
const web3 = new Web3(this.chain.rpcUrl);
|
||||
const pythContract = new web3.eth.Contract(EXTENDED_PYTH_ABI, this.address);
|
||||
return pythContract;
|
||||
}
|
||||
|
||||
async getPriceFeed(feedId: string) {
|
||||
const pythContract = this.getContract();
|
||||
const [price, conf, expo, publishTime] = await pythContract.methods
|
||||
.getPriceUnsafe(feedId)
|
||||
.call();
|
||||
return { price, conf, expo, publishTime };
|
||||
}
|
||||
|
||||
async getValidTimePeriod() {
|
||||
const pythContract = this.getContract();
|
||||
const result = await pythContract.methods.getValidTimePeriod().call();
|
||||
return Number(result);
|
||||
}
|
||||
|
||||
async getBaseUpdateFee() {
|
||||
const pythContract = this.getContract();
|
||||
const result = await pythContract.methods.singleUpdateFeeInWei().call();
|
||||
return { amount: result };
|
||||
}
|
||||
|
||||
async getDataSources(): Promise<DataSource[]> {
|
||||
const pythContract = this.getContract();
|
||||
const result = await pythContract.methods.validDataSources().call();
|
||||
return result.map(
|
||||
({
|
||||
chainId,
|
||||
emitterAddress,
|
||||
}: {
|
||||
chainId: string;
|
||||
emitterAddress: string;
|
||||
}) => {
|
||||
return {
|
||||
emitterChain: Number(chainId),
|
||||
emitterAddress: emitterAddress.replace("0x", ""),
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async getGovernanceDataSource(): Promise<DataSource> {
|
||||
const pythContract = this.getContract();
|
||||
const [chainId, emitterAddress] = await pythContract.methods
|
||||
.governanceDataSource()
|
||||
.call();
|
||||
return {
|
||||
emitterChain: Number(chainId),
|
||||
emitterAddress: emitterAddress.replace("0x", ""),
|
||||
};
|
||||
}
|
||||
|
||||
async executeGovernanceInstruction(privateKey: string, vaa: Buffer) {
|
||||
const web3 = new Web3(this.chain.rpcUrl);
|
||||
const { address } = web3.eth.accounts.wallet.add(privateKey);
|
||||
const pythContract = new web3.eth.Contract(EXTENDED_PYTH_ABI, this.address);
|
||||
const transactionObject = pythContract.methods.executeGovernanceInstruction(
|
||||
"0x" + vaa.toString("hex")
|
||||
);
|
||||
const gasEstiamte = await transactionObject.estimateGas({
|
||||
from: address,
|
||||
gas: 100000000,
|
||||
});
|
||||
return transactionObject.send({ from: address, gas: gasEstiamte * 2 });
|
||||
}
|
||||
|
||||
getChain(): EVMChain {
|
||||
return this.chain;
|
||||
}
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
chain: this.chain.id,
|
||||
address: this.address,
|
||||
type: EVMContract.type,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -20,6 +20,7 @@ import SquadsMesh from "@sqds/mesh";
|
|||
import { AnchorProvider, Wallet } from "@coral-xyz/anchor/dist/cjs/provider";
|
||||
import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet";
|
||||
import {
|
||||
decodeGovernancePayload,
|
||||
executeProposal,
|
||||
MultisigVault,
|
||||
WORMHOLE_ADDRESS,
|
||||
|
@ -32,6 +33,8 @@ import {
|
|||
deriveWormholeBridgeDataKey,
|
||||
} from "@certusone/wormhole-sdk/lib/cjs/solana/wormhole";
|
||||
import { Storable } from "./base";
|
||||
import { parseVaa } from "@certusone/wormhole-sdk";
|
||||
import { DefaultStore } from "./store";
|
||||
|
||||
class InvalidTransactionError extends Error {
|
||||
constructor(message: string) {
|
||||
|
@ -125,6 +128,33 @@ export class SubmittedWormholeMessage {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A general executor that tries to find any contract that can execute a given VAA and executes it
|
||||
* @param senderPrivateKey the private key to execute the governance instruction with
|
||||
* @param vaa the VAA to execute
|
||||
*/
|
||||
export async function executeVaa(senderPrivateKey: string, vaa: Buffer) {
|
||||
const parsedVaa = parseVaa(vaa);
|
||||
const action = decodeGovernancePayload(parsedVaa.payload);
|
||||
if (!action) return; //TODO: handle other actions
|
||||
for (const contract of Object.values(DefaultStore.contracts)) {
|
||||
if (
|
||||
action.targetChainId === "unset" ||
|
||||
contract.getChain().wormholeChainName === action.targetChainId
|
||||
) {
|
||||
const governanceSource = await contract.getGovernanceDataSource();
|
||||
if (
|
||||
governanceSource.emitterAddress ===
|
||||
parsedVaa.emitterAddress.toString("hex") &&
|
||||
governanceSource.emitterChain === parsedVaa.emitterChain
|
||||
) {
|
||||
// TODO: check governance sequence number as well
|
||||
await contract.executeGovernanceInstruction(senderPrivateKey, vaa);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function asPythCluster(cluster: string): PythCluster {
|
||||
const pythCluster = cluster as PythCluster;
|
||||
getPythClusterApiUrl(pythCluster); // throws if cluster is invalid
|
||||
|
@ -206,26 +236,32 @@ export class WormholeMultiSigTransaction {
|
|||
|
||||
async getState() {
|
||||
const proposal = await this.squad.getTransaction(this.address);
|
||||
return proposal.status; //TODO: make it typed and parse the status to a more readable format
|
||||
// Converts the status object to a string e.g
|
||||
// { "active":{} } => "active"
|
||||
return Object.keys(proposal.status)[0];
|
||||
}
|
||||
|
||||
async execute(): Promise<SubmittedWormholeMessage> {
|
||||
async execute(): Promise<SubmittedWormholeMessage[]> {
|
||||
const proposal = await this.squad.getTransaction(this.address);
|
||||
const signatures = await executeProposal(
|
||||
proposal,
|
||||
this.squad,
|
||||
this.cluster
|
||||
);
|
||||
const msgs: SubmittedWormholeMessage[] = [];
|
||||
for (const signature of signatures) {
|
||||
try {
|
||||
return SubmittedWormholeMessage.fromTransactionSignature(
|
||||
signature,
|
||||
this.cluster
|
||||
msgs.push(
|
||||
await SubmittedWormholeMessage.fromTransactionSignature(
|
||||
signature,
|
||||
this.cluster
|
||||
)
|
||||
);
|
||||
} catch (e: any) {
|
||||
if (!(e instanceof InvalidTransactionError)) throw e;
|
||||
}
|
||||
}
|
||||
if (msgs.length > 0) return msgs;
|
||||
throw new Error("No transactions with wormhole messages found");
|
||||
}
|
||||
}
|
||||
|
@ -284,7 +320,7 @@ export class Vault extends Storable {
|
|||
}
|
||||
|
||||
public async proposeWormholeMessage(
|
||||
payload: Buffer
|
||||
payloads: Buffer[]
|
||||
): Promise<WormholeMultiSigTransaction> {
|
||||
const squad = this.getSquadOrThrow();
|
||||
const multisigVault = new MultisigVault(
|
||||
|
@ -293,10 +329,11 @@ export class Vault extends Storable {
|
|||
squad,
|
||||
this.key
|
||||
);
|
||||
const txAccount = await multisigVault.proposeWormholeMessageWithPayer(
|
||||
payload,
|
||||
squad.wallet.publicKey
|
||||
);
|
||||
const txAccount =
|
||||
await multisigVault.proposeWormholeMultipleMessagesWithPayer(
|
||||
payloads,
|
||||
squad.wallet.publicKey
|
||||
);
|
||||
return new WormholeMultiSigTransaction(txAccount, squad, this.cluster);
|
||||
}
|
||||
}
|
|
@ -6,10 +6,10 @@ repl.setService(service);
|
|||
repl.start();
|
||||
repl.evalCode(
|
||||
"import { loadHotWallet, Vault } from './src/entities';" +
|
||||
"import { SuiChain, CosmWasmChain, AptosChain, EVMChain } from './src/chains';" +
|
||||
"import { SuiChain, CosmWasmChain, AptosChain, EvmChain } from './src/chains';" +
|
||||
"import { SuiContract } from './src/sui';" +
|
||||
"import { CosmWasmContract } from './src/cosmwasm';" +
|
||||
"import { EVMContract } from './src/evm';" +
|
||||
"import { EvmContract } from './src/evm';" +
|
||||
"import { AptosContract } from './src/aptos';" +
|
||||
"import { DefaultStore } from './src/store';" +
|
||||
"DefaultStore"
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
import { AptosChain, Chain, CosmWasmChain, EVMChain, SuiChain } from "./chains";
|
||||
import { CosmWasmContract } from "./cosmwasm";
|
||||
import { SuiContract } from "./sui";
|
||||
import {
|
||||
AptosChain,
|
||||
Chain,
|
||||
CosmWasmChain,
|
||||
EvmChain,
|
||||
GlobalChain,
|
||||
SuiChain,
|
||||
} from "./chains";
|
||||
import { CosmWasmContract } from "./contracts/cosmwasm";
|
||||
import { SuiContract } from "./contracts/sui";
|
||||
import { Contract } from "./base";
|
||||
import { parse, stringify } from "yaml";
|
||||
import {
|
||||
|
@ -11,12 +18,12 @@ import {
|
|||
statSync,
|
||||
writeFileSync,
|
||||
} from "fs";
|
||||
import { EVMContract } from "./evm";
|
||||
import { AptosContract } from "./aptos";
|
||||
import { Vault } from "./entities";
|
||||
import { EvmContract } from "./contracts/evm";
|
||||
import { AptosContract } from "./contracts/aptos";
|
||||
import { Vault } from "./governance";
|
||||
|
||||
class Store {
|
||||
public chains: Record<string, Chain> = {};
|
||||
public chains: Record<string, Chain> = { global: new GlobalChain() };
|
||||
public contracts: Record<string, Contract> = {};
|
||||
public vaults: Record<string, Vault> = {};
|
||||
|
||||
|
@ -76,7 +83,7 @@ class Store {
|
|||
let allChainClasses = {
|
||||
[CosmWasmChain.type]: CosmWasmChain,
|
||||
[SuiChain.type]: SuiChain,
|
||||
[EVMChain.type]: EVMChain,
|
||||
[EvmChain.type]: EvmChain,
|
||||
[AptosChain.type]: AptosChain,
|
||||
};
|
||||
|
||||
|
@ -94,7 +101,7 @@ class Store {
|
|||
let allContractClasses = {
|
||||
[CosmWasmContract.type]: CosmWasmContract,
|
||||
[SuiContract.type]: SuiContract,
|
||||
[EVMContract.type]: EVMContract,
|
||||
[EvmContract.type]: EvmContract,
|
||||
[AptosContract.type]: AptosContract,
|
||||
};
|
||||
this.getYamlFiles(`${this.path}/contracts/`).forEach((yamlFile) => {
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
id: aptos_mainnet
|
||||
wormholeChainName: aptos
|
||||
mainnet: true
|
||||
rpcUrl: https://fullnode.mainnet.aptoslabs.com/v1
|
||||
type: AptosChain
|
|
@ -1,3 +1,5 @@
|
|||
id: aptos_testnet
|
||||
wormholeChainName: aptos
|
||||
mainnet: false
|
||||
rpcUrl: https://fullnode.testnet.aptoslabs.com/v1
|
||||
type: AptosChain
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
querierEndpoint: https://rpc.uni.junonetwork.io/
|
||||
executorEndpoint: https://rpc.uni.junonetwork.io/
|
||||
id: juno_testnet
|
||||
wormholeChainName: juno_testnet
|
||||
mainnet: false
|
||||
gasPrice: "0.025"
|
||||
prefix: juno
|
||||
feeDenom: ujunox
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
querierEndpoint: https://rpc-kralum.neutron-1.neutron.org
|
||||
executorEndpoint: https://rpc-kralum.neutron-1.neutron.org
|
||||
id: neutron
|
||||
wormholeChainName: neutron
|
||||
mainnet: true
|
||||
gasPrice: "0.025"
|
||||
prefix: neutron
|
||||
feeDenom: untrn
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
querierEndpoint: https://rpc-palvus.pion-1.ntrn.tech/
|
||||
executorEndpoint: https://rpc-palvus.pion-1.ntrn.tech/
|
||||
id: neutron_testnet_pion_1
|
||||
wormholeChainName: neutron_testnet_pion_1
|
||||
mainnet: false
|
||||
gasPrice: "0.05"
|
||||
prefix: neutron
|
||||
feeDenom: untrn
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
querierEndpoint: https://rpc.osmotest5.osmosis.zone/
|
||||
executorEndpoint: https://rpc.osmotest5.osmosis.zone/
|
||||
id: osmosis_testnet_5
|
||||
wormholeChainName: osmosis_testnet_5
|
||||
mainnet: false
|
||||
gasPrice: "0.025"
|
||||
prefix: osmo
|
||||
feeDenom: uosmo
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
querierEndpoint: https://sei-rpc.polkachu.com
|
||||
executorEndpoint: https://sei-rpc.polkachu.com
|
||||
id: sei_pacific_1
|
||||
wormholeChainName: sei_pacific_1
|
||||
mainnet: true
|
||||
gasPrice: "0.025"
|
||||
prefix: sei
|
||||
feeDenom: usei
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
querierEndpoint: https://rpc.atlantic-2.seinetwork.io/
|
||||
executorEndpoint: https://rpc.atlantic-2.seinetwork.io/
|
||||
id: sei_testnet_atlantic_2
|
||||
wormholeChainName: sei_testnet_atlantic_2
|
||||
mainnet: false
|
||||
gasPrice: "0.01"
|
||||
prefix: sei
|
||||
feeDenom: usei
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
id: arbitrum_testnet
|
||||
rpcUrl: https://goerli-rollup.arbitrum.io/rpc
|
||||
type: EVMChain
|
|
@ -1,3 +0,0 @@
|
|||
id: cronos
|
||||
rpcUrl: https://cronosrpc-1.xstaking.sg
|
||||
type: EVMChain
|
|
@ -1,3 +0,0 @@
|
|||
id: cronos_testnet
|
||||
rpcUrl: https://evm-t3.cronos.org
|
||||
type: EVMChain
|
|
@ -0,0 +1,6 @@
|
|||
id: arbitrum
|
||||
wormholeChainName: arbitrum
|
||||
mainnet: true
|
||||
rpcUrl: https://arb1.arbitrum.io/rpc
|
||||
networkId: 42161
|
||||
type: EvmChain
|
|
@ -0,0 +1,6 @@
|
|||
id: arbitrum_testnet
|
||||
wormholeChainName: arbitrum
|
||||
mainnet: false
|
||||
rpcUrl: https://goerli-rollup.arbitrum.io/rpc
|
||||
networkId: 421613
|
||||
type: EvmChain
|
|
@ -0,0 +1,6 @@
|
|||
id: aurora
|
||||
wormholeChainName: aurora
|
||||
mainnet: true
|
||||
rpcUrl: https://mainnet.aurora.dev
|
||||
networkId: 1313161554
|
||||
type: EvmChain
|
|
@ -0,0 +1,6 @@
|
|||
id: aurora_testnet
|
||||
wormholeChainName: aurora
|
||||
mainnet: false
|
||||
rpcUrl: https://testnet.aurora.dev
|
||||
networkId: 1313161555
|
||||
type: EvmChain
|
|
@ -0,0 +1,6 @@
|
|||
id: avalanche
|
||||
wormholeChainName: avalanche
|
||||
mainnet: true
|
||||
rpcUrl: https://api.avax.network/ext/bc/C/rpc
|
||||
networkId: 43114
|
||||
type: EvmChain
|
|
@ -0,0 +1,6 @@
|
|||
id: base_goerli
|
||||
wormholeChainName: base
|
||||
mainnet: false
|
||||
rpcUrl: https://goerli.base.org
|
||||
networkId: 84531
|
||||
type: EvmChain
|
|
@ -0,0 +1,6 @@
|
|||
id: bnb
|
||||
wormholeChainName: bsc
|
||||
mainnet: true
|
||||
rpcUrl: https://rpc.ankr.com/bsc
|
||||
networkId: 56
|
||||
type: EvmChain
|
|
@ -0,0 +1,6 @@
|
|||
id: bnb_testnet
|
||||
wormholeChainName: bsc
|
||||
mainnet: false
|
||||
rpcUrl: https://rpc.ankr.com/bsc_testnet_chapel
|
||||
networkId: 97
|
||||
type: EvmChain
|
|
@ -0,0 +1,6 @@
|
|||
id: canto
|
||||
wormholeChainName: canto
|
||||
mainnet: true
|
||||
rpcUrl: https://canto.gravitychain.io
|
||||
networkId: 7700
|
||||
type: EvmChain
|
|
@ -0,0 +1,6 @@
|
|||
id: canto_testnet
|
||||
wormholeChainName: canto
|
||||
mainnet: false
|
||||
rpcUrl: https://canto-testnet.plexnode.wtf
|
||||
networkId: 7701
|
||||
type: EvmChain
|
|
@ -0,0 +1,6 @@
|
|||
id: celo
|
||||
wormholeChainName: celo
|
||||
mainnet: true
|
||||
rpcUrl: https://forno.celo.org
|
||||
networkId: 42220
|
||||
type: EvmChain
|
|
@ -0,0 +1,6 @@
|
|||
id: celo_alfajores_testnet
|
||||
wormholeChainName: celo
|
||||
mainnet: false
|
||||
rpcUrl: https://alfajores-forno.celo-testnet.org
|
||||
networkId: 44787
|
||||
type: EvmChain
|
|
@ -0,0 +1,6 @@
|
|||
id: chiado
|
||||
wormholeChainName: gnosis
|
||||
mainnet: true
|
||||
rpcUrl: https://rpc.chiadochain.net
|
||||
networkId: 10200
|
||||
type: EvmChain
|
|
@ -0,0 +1,6 @@
|
|||
id: conflux_espace
|
||||
wormholeChainName: conflux_espace
|
||||
mainnet: true
|
||||
rpcUrl: https://evm.confluxrpc.org
|
||||
networkId: 1030
|
||||
type: EvmChain
|
|
@ -0,0 +1,6 @@
|
|||
id: conflux_espace_testnet
|
||||
wormholeChainName: conflux_espace
|
||||
mainnet: false
|
||||
rpcUrl: https://evmtestnet.confluxrpc.com
|
||||
networkId: 71
|
||||
type: EvmChain
|
|
@ -0,0 +1,6 @@
|
|||
id: cronos
|
||||
wormholeChainName: cronos
|
||||
mainnet: true
|
||||
rpcUrl: https://cronosrpc-1.xstaking.sg
|
||||
networkId: 25
|
||||
type: EvmChain
|
|
@ -0,0 +1,6 @@
|
|||
id: cronos_testnet
|
||||
wormholeChainName: cronos
|
||||
mainnet: false
|
||||
rpcUrl: https://evm-t3.cronos.org
|
||||
networkId: 338
|
||||
type: EvmChain
|
|
@ -0,0 +1,6 @@
|
|||
id: ethereum
|
||||
wormholeChainName: ethereum
|
||||
mainnet: true
|
||||
rpcUrl: https://eth.llamarpc.com
|
||||
networkId: 1
|
||||
type: EvmChain
|
|
@ -0,0 +1,6 @@
|
|||
id: evmos
|
||||
wormholeChainName: evmos
|
||||
mainnet: true
|
||||
rpcUrl: https://eth.bd.evmos.org:8545/
|
||||
networkId: 9001
|
||||
type: EvmChain
|
|
@ -0,0 +1,6 @@
|
|||
id: evmos_testnet
|
||||
wormholeChainName: evmos
|
||||
mainnet: false
|
||||
rpcUrl: https://eth.bd.evmos.dev:8545/
|
||||
networkId: 9000
|
||||
type: EvmChain
|
|
@ -0,0 +1,6 @@
|
|||
id: fantom
|
||||
wormholeChainName: fantom
|
||||
mainnet: true
|
||||
rpcUrl: https://rpc.ankr.com/fantom
|
||||
networkId: 250
|
||||
type: EvmChain
|
|
@ -0,0 +1,6 @@
|
|||
id: fantom_testnet
|
||||
wormholeChainName: fantom
|
||||
mainnet: false
|
||||
rpcUrl: https://rpc.ankr.com/fantom_testnet
|
||||
networkId: 4002
|
||||
type: EvmChain
|
|
@ -0,0 +1,6 @@
|
|||
id: fuji
|
||||
wormholeChainName: avalanche
|
||||
mainnet: true
|
||||
rpcUrl: https://api.avax-test.network/ext/bc/C/rpc
|
||||
networkId: 43113
|
||||
type: EvmChain
|
|
@ -0,0 +1,6 @@
|
|||
id: gnosis
|
||||
wormholeChainName: gnosis
|
||||
mainnet: true
|
||||
rpcUrl: https://rpc.gnosischain.com
|
||||
networkId: 100
|
||||
type: EvmChain
|
|
@ -0,0 +1,6 @@
|
|||
id: goerli
|
||||
wormholeChainName: ethereum
|
||||
mainnet: false
|
||||
rpcUrl: https://rpc.goerli.eth.gateway.fm
|
||||
networkId: 5
|
||||
type: EvmChain
|
|
@ -0,0 +1,6 @@
|
|||
id: kava
|
||||
wormholeChainName: kava
|
||||
mainnet: true
|
||||
rpcUrl: https://evm.kava.io
|
||||
networkId: 2222
|
||||
type: EvmChain
|
|
@ -0,0 +1,6 @@
|
|||
id: kava_testnet
|
||||
wormholeChainName: kava
|
||||
mainnet: false
|
||||
rpcUrl: https://evm.testnet.kava.io
|
||||
networkId: 2221
|
||||
type: EvmChain
|
|
@ -0,0 +1,6 @@
|
|||
id: kcc
|
||||
wormholeChainName: kcc
|
||||
mainnet: true
|
||||
rpcUrl: https://rpc-mainnet.kcc.network
|
||||
networkId: 321
|
||||
type: EvmChain
|
|
@ -0,0 +1,6 @@
|
|||
id: kcc_testnet
|
||||
wormholeChainName: kcc
|
||||
mainnet: false
|
||||
rpcUrl: https://rpc-testnet.kcc.network
|
||||
networkId: 322
|
||||
type: EvmChain
|
|
@ -0,0 +1,6 @@
|
|||
id: linea
|
||||
wormholeChainName: linea
|
||||
mainnet: true
|
||||
rpcUrl: https://linea.rpc.thirdweb.com
|
||||
networkId: 59144
|
||||
type: EvmChain
|
|
@ -0,0 +1,6 @@
|
|||
id: linea_goerli
|
||||
wormholeChainName: linea
|
||||
mainnet: false
|
||||
rpcUrl: https://rpc.goerli.linea.build
|
||||
networkId: 59140
|
||||
type: EvmChain
|
|
@ -0,0 +1,6 @@
|
|||
id: mantle
|
||||
wormholeChainName: mantle
|
||||
mainnet: true
|
||||
rpcUrl: https://rpc.mantle.xyz/
|
||||
networkId: 5000
|
||||
type: EvmChain
|
|
@ -0,0 +1,6 @@
|
|||
id: mantle_testnet
|
||||
wormholeChainName: mantle
|
||||
mainnet: false
|
||||
rpcUrl: https://rpc.testnet.mantle.xyz/
|
||||
networkId: 5001
|
||||
type: EvmChain
|
|
@ -0,0 +1,6 @@
|
|||
id: meter
|
||||
wormholeChainName: meter
|
||||
mainnet: true
|
||||
rpcUrl: https://rpc-meter.jellypool.xyz
|
||||
networkId: 82
|
||||
type: EvmChain
|
|
@ -0,0 +1,6 @@
|
|||
id: meter_testnet
|
||||
wormholeChainName: meter
|
||||
mainnet: false
|
||||
rpcUrl: https://rpctest.meter.io
|
||||
networkId: 83
|
||||
type: EvmChain
|
|
@ -0,0 +1,6 @@
|
|||
id: mumbai
|
||||
wormholeChainName: polygon
|
||||
mainnet: false
|
||||
rpcUrl: https://polygon-testnet-rpc.allthatnode.com:8545
|
||||
networkId: 80001
|
||||
type: EvmChain
|
|
@ -0,0 +1,6 @@
|
|||
id: neon
|
||||
wormholeChainName: neon
|
||||
mainnet: true
|
||||
rpcUrl: https://neon-proxy-mainnet.solana.p2p.org
|
||||
networkId: 245022934
|
||||
type: EvmChain
|
|
@ -0,0 +1,6 @@
|
|||
id: neon_devnet
|
||||
wormholeChainName: neon
|
||||
mainnet: false
|
||||
rpcUrl: https://devnet.neonevm.org
|
||||
networkId: 245022926
|
||||
type: EvmChain
|
|
@ -0,0 +1,6 @@
|
|||
id: oasis
|
||||
wormholeChainName: oasis
|
||||
mainnet: true
|
||||
rpcUrl: https://emerald.oasis.dev/
|
||||
networkId: 42262
|
||||
type: EvmChain
|
|
@ -0,0 +1,6 @@
|
|||
id: optimism
|
||||
wormholeChainName: optimism
|
||||
mainnet: true
|
||||
rpcUrl: https://rpc.ankr.com/optimism
|
||||
networkId: 10
|
||||
type: EvmChain
|
|
@ -0,0 +1,6 @@
|
|||
id: optimism_goerli
|
||||
wormholeChainName: optimism
|
||||
mainnet: false
|
||||
rpcUrl: https://rpc.ankr.com/optimism_testnet
|
||||
networkId: 420
|
||||
type: EvmChain
|
|
@ -0,0 +1,6 @@
|
|||
id: polygon
|
||||
wormholeChainName: polygon
|
||||
mainnet: true
|
||||
rpcUrl: https://polygon-rpc.com
|
||||
networkId: 137
|
||||
type: EvmChain
|
|
@ -0,0 +1,6 @@
|
|||
id: polygon_zkevm
|
||||
wormholeChainName: polygon_zkevm
|
||||
mainnet: true
|
||||
rpcUrl: https://zkevm-rpc.com
|
||||
networkId: 1101
|
||||
type: EvmChain
|
|
@ -0,0 +1,6 @@
|
|||
id: polygon_zkevm_testnet
|
||||
wormholeChainName: polygon_zkevm
|
||||
mainnet: false
|
||||
rpcUrl: https://rpc.public.zkevm-test.net/
|
||||
networkId: 1442
|
||||
type: EvmChain
|
|
@ -0,0 +1,6 @@
|
|||
id: wemix
|
||||
wormholeChainName: wemix
|
||||
mainnet: true
|
||||
rpcUrl: https://api.wemix.com
|
||||
networkId: 1111
|
||||
type: EvmChain
|
|
@ -0,0 +1,6 @@
|
|||
id: wemix_testnet
|
||||
wormholeChainName: wemix
|
||||
mainnet: false
|
||||
rpcUrl: https://api.test.wemix.com
|
||||
networkId: 1112
|
||||
type: EvmChain
|
|
@ -0,0 +1,6 @@
|
|||
id: zksync
|
||||
wormholeChainName: zksync
|
||||
mainnet: true
|
||||
rpcUrl: https://zksync2-mainnet.zksync.io
|
||||
networkId: 324
|
||||
type: EvmChain
|
|
@ -0,0 +1,6 @@
|
|||
id: zksync_goerli
|
||||
wormholeChainName: zksync
|
||||
mainnet: false
|
||||
rpcUrl: https://zksync2-testnet.zksync.dev
|
||||
networkId: 280
|
||||
type: EvmChain
|
|
@ -1,3 +1,5 @@
|
|||
id: sui_devnet
|
||||
wormholeChainName: sui
|
||||
mainnet: false
|
||||
rpcUrl: https://fullnode.devnet.sui.io:443
|
||||
type: SuiChain
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
id: sui_mainnet
|
||||
wormholeChainName: sui
|
||||
mainnet: true
|
||||
rpcUrl: https://fullnode.mainnet.sui.io:443
|
||||
type: SuiChain
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
id: sui_testnet
|
||||
wormholeChainName: sui
|
||||
mainnet: false
|
||||
rpcUrl: https://fullnode.testnet.sui.io:443
|
||||
type: SuiChain
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
chain: aptos_mainnet
|
||||
stateId: "0x7e783b349d3e89cf5931af376ebeadbfab855b3fa239b7ada8f5a92fbea6b387"
|
||||
wormholeStateId: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625"
|
||||
type: AptosContract
|
|
@ -1,3 +0,0 @@
|
|||
chain: cronos
|
||||
address: "0xe0d0e68297772dd5a1f1d99897c581e2082dba5b"
|
||||
type: EVMContract
|
|
@ -1,3 +0,0 @@
|
|||
chain: cronos_testnet
|
||||
address: "0xFF125F377F9F7631a05f4B01CeD32a6A2ab843C7"
|
||||
type: EVMContract
|
|
@ -0,0 +1,3 @@
|
|||
chain: arbitrum
|
||||
address: "0xff1a0f4744e8582DF1aE09D5611b887B6a12925C"
|
||||
type: EvmContract
|
|
@ -0,0 +1,3 @@
|
|||
chain: arbitrum_testnet
|
||||
address: "0x939C0e902FF5B3F7BA666Cc8F6aC75EE76d3f900"
|
||||
type: EvmContract
|
|
@ -0,0 +1,3 @@
|
|||
chain: aurora
|
||||
address: "0xF89C7b475821EC3fDC2dC8099032c05c6c0c9AB9"
|
||||
type: EvmContract
|
|
@ -0,0 +1,3 @@
|
|||
chain: aurora_testnet
|
||||
address: "0x4305FB66699C3B2702D4d05CF36551390A4c69C6"
|
||||
type: EvmContract
|
|
@ -0,0 +1,3 @@
|
|||
chain: avalanche
|
||||
address: "0x4305FB66699C3B2702D4d05CF36551390A4c69C6"
|
||||
type: EvmContract
|
|
@ -0,0 +1,3 @@
|
|||
chain: base_goerli
|
||||
address: "0x5955C1478F0dAD753C7E2B4dD1b4bC530C64749f"
|
||||
type: EvmContract
|
|
@ -0,0 +1,3 @@
|
|||
chain: bnb
|
||||
address: "0x4D7E825f80bDf85e913E0DD2A2D54927e9dE1594"
|
||||
type: EvmContract
|
|
@ -0,0 +1,3 @@
|
|||
chain: bnb_testnet
|
||||
address: "0xd7308b14BF4008e7C7196eC35610B1427C5702EA"
|
||||
type: EvmContract
|
|
@ -0,0 +1,3 @@
|
|||
chain: canto
|
||||
address: "0x98046Bd286715D3B0BC227Dd7a956b83D8978603"
|
||||
type: EvmContract
|
|
@ -0,0 +1,3 @@
|
|||
chain: canto_testnet
|
||||
address: "0xA2aa501b19aff244D90cc15a4Cf739D2725B5729"
|
||||
type: EvmContract
|
|
@ -0,0 +1,3 @@
|
|||
chain: celo
|
||||
address: "0xff1a0f4744e8582DF1aE09D5611b887B6a12925C"
|
||||
type: EvmContract
|
|
@ -0,0 +1,3 @@
|
|||
chain: celo_alfajores_testnet
|
||||
address: "0xff1a0f4744e8582DF1aE09D5611b887B6a12925C"
|
||||
type: EvmContract
|
|
@ -0,0 +1,3 @@
|
|||
chain: chiado
|
||||
address: "0xdDAf6D29b8bc81c1F0798a5e4c264ae89c16a72B"
|
||||
type: EvmContract
|
|
@ -0,0 +1,3 @@
|
|||
chain: conflux_espace
|
||||
address: "0xe9d69CdD6Fe41e7B621B4A688C5D1a68cB5c8ADc"
|
||||
type: EvmContract
|
|
@ -0,0 +1,3 @@
|
|||
chain: conflux_espace_testnet
|
||||
address: "0xA2aa501b19aff244D90cc15a4Cf739D2725B5729"
|
||||
type: EvmContract
|
|
@ -0,0 +1,3 @@
|
|||
chain: cronos
|
||||
address: "0xE0d0e68297772Dd5a1f1D99897c581E2082dbA5B"
|
||||
type: EvmContract
|
|
@ -0,0 +1,3 @@
|
|||
chain: cronos_testnet
|
||||
address: "0xBAEA4A1A2Eaa4E9bb78f2303C213Da152933170E"
|
||||
type: EvmContract
|
|
@ -0,0 +1,3 @@
|
|||
chain: ethereum
|
||||
address: "0x4305FB66699C3B2702D4d05CF36551390A4c69C6"
|
||||
type: EvmContract
|
|
@ -0,0 +1,3 @@
|
|||
chain: evmos
|
||||
address: "0x354bF866A4B006C9AF9d9e06d9364217A8616E12"
|
||||
type: EvmContract
|
|
@ -0,0 +1,3 @@
|
|||
chain: evmos_testnet
|
||||
address: "0x354bF866A4B006C9AF9d9e06d9364217A8616E12"
|
||||
type: EvmContract
|
|
@ -0,0 +1,3 @@
|
|||
chain: fantom
|
||||
address: "0xff1a0f4744e8582DF1aE09D5611b887B6a12925C"
|
||||
type: EvmContract
|
|
@ -0,0 +1,3 @@
|
|||
chain: fantom_testnet
|
||||
address: "0xff1a0f4744e8582DF1aE09D5611b887B6a12925C"
|
||||
type: EvmContract
|
|
@ -0,0 +1,3 @@
|
|||
chain: fuji
|
||||
address: "0xff1a0f4744e8582DF1aE09D5611b887B6a12925C"
|
||||
type: EvmContract
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue