[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:
Mohammad Amin Khashkhashi Moghaddam 2023-07-25 08:14:31 +02:00 committed by GitHub
parent db6bba9526
commit 31ad2b66a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
133 changed files with 1462 additions and 378 deletions

View File

@ -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))

View File

@ -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": "*",

View File

@ -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

View File

@ -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
);
}
}

View File

@ -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,

View File

@ -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,

View File

@ -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,
};
}
}

View File

@ -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 }));
}

View File

@ -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,
};
}
}

View File

@ -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);
}
}

View File

@ -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"

View File

@ -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) => {

View File

@ -0,0 +1,5 @@
id: aptos_mainnet
wormholeChainName: aptos
mainnet: true
rpcUrl: https://fullnode.mainnet.aptoslabs.com/v1
type: AptosChain

View File

@ -1,3 +1,5 @@
id: aptos_testnet
wormholeChainName: aptos
mainnet: false
rpcUrl: https://fullnode.testnet.aptoslabs.com/v1
type: AptosChain

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,3 +0,0 @@
id: arbitrum_testnet
rpcUrl: https://goerli-rollup.arbitrum.io/rpc
type: EVMChain

View File

@ -1,3 +0,0 @@
id: cronos
rpcUrl: https://cronosrpc-1.xstaking.sg
type: EVMChain

View File

@ -1,3 +0,0 @@
id: cronos_testnet
rpcUrl: https://evm-t3.cronos.org
type: EVMChain

View File

@ -0,0 +1,6 @@
id: arbitrum
wormholeChainName: arbitrum
mainnet: true
rpcUrl: https://arb1.arbitrum.io/rpc
networkId: 42161
type: EvmChain

View File

@ -0,0 +1,6 @@
id: arbitrum_testnet
wormholeChainName: arbitrum
mainnet: false
rpcUrl: https://goerli-rollup.arbitrum.io/rpc
networkId: 421613
type: EvmChain

View File

@ -0,0 +1,6 @@
id: aurora
wormholeChainName: aurora
mainnet: true
rpcUrl: https://mainnet.aurora.dev
networkId: 1313161554
type: EvmChain

View File

@ -0,0 +1,6 @@
id: aurora_testnet
wormholeChainName: aurora
mainnet: false
rpcUrl: https://testnet.aurora.dev
networkId: 1313161555
type: EvmChain

View File

@ -0,0 +1,6 @@
id: avalanche
wormholeChainName: avalanche
mainnet: true
rpcUrl: https://api.avax.network/ext/bc/C/rpc
networkId: 43114
type: EvmChain

View File

@ -0,0 +1,6 @@
id: base_goerli
wormholeChainName: base
mainnet: false
rpcUrl: https://goerli.base.org
networkId: 84531
type: EvmChain

View File

@ -0,0 +1,6 @@
id: bnb
wormholeChainName: bsc
mainnet: true
rpcUrl: https://rpc.ankr.com/bsc
networkId: 56
type: EvmChain

View File

@ -0,0 +1,6 @@
id: bnb_testnet
wormholeChainName: bsc
mainnet: false
rpcUrl: https://rpc.ankr.com/bsc_testnet_chapel
networkId: 97
type: EvmChain

View File

@ -0,0 +1,6 @@
id: canto
wormholeChainName: canto
mainnet: true
rpcUrl: https://canto.gravitychain.io
networkId: 7700
type: EvmChain

View File

@ -0,0 +1,6 @@
id: canto_testnet
wormholeChainName: canto
mainnet: false
rpcUrl: https://canto-testnet.plexnode.wtf
networkId: 7701
type: EvmChain

View File

@ -0,0 +1,6 @@
id: celo
wormholeChainName: celo
mainnet: true
rpcUrl: https://forno.celo.org
networkId: 42220
type: EvmChain

View File

@ -0,0 +1,6 @@
id: celo_alfajores_testnet
wormholeChainName: celo
mainnet: false
rpcUrl: https://alfajores-forno.celo-testnet.org
networkId: 44787
type: EvmChain

View File

@ -0,0 +1,6 @@
id: chiado
wormholeChainName: gnosis
mainnet: true
rpcUrl: https://rpc.chiadochain.net
networkId: 10200
type: EvmChain

View File

@ -0,0 +1,6 @@
id: conflux_espace
wormholeChainName: conflux_espace
mainnet: true
rpcUrl: https://evm.confluxrpc.org
networkId: 1030
type: EvmChain

View File

@ -0,0 +1,6 @@
id: conflux_espace_testnet
wormholeChainName: conflux_espace
mainnet: false
rpcUrl: https://evmtestnet.confluxrpc.com
networkId: 71
type: EvmChain

View File

@ -0,0 +1,6 @@
id: cronos
wormholeChainName: cronos
mainnet: true
rpcUrl: https://cronosrpc-1.xstaking.sg
networkId: 25
type: EvmChain

View File

@ -0,0 +1,6 @@
id: cronos_testnet
wormholeChainName: cronos
mainnet: false
rpcUrl: https://evm-t3.cronos.org
networkId: 338
type: EvmChain

View File

@ -0,0 +1,6 @@
id: ethereum
wormholeChainName: ethereum
mainnet: true
rpcUrl: https://eth.llamarpc.com
networkId: 1
type: EvmChain

View File

@ -0,0 +1,6 @@
id: evmos
wormholeChainName: evmos
mainnet: true
rpcUrl: https://eth.bd.evmos.org:8545/
networkId: 9001
type: EvmChain

View File

@ -0,0 +1,6 @@
id: evmos_testnet
wormholeChainName: evmos
mainnet: false
rpcUrl: https://eth.bd.evmos.dev:8545/
networkId: 9000
type: EvmChain

View File

@ -0,0 +1,6 @@
id: fantom
wormholeChainName: fantom
mainnet: true
rpcUrl: https://rpc.ankr.com/fantom
networkId: 250
type: EvmChain

View File

@ -0,0 +1,6 @@
id: fantom_testnet
wormholeChainName: fantom
mainnet: false
rpcUrl: https://rpc.ankr.com/fantom_testnet
networkId: 4002
type: EvmChain

View File

@ -0,0 +1,6 @@
id: fuji
wormholeChainName: avalanche
mainnet: true
rpcUrl: https://api.avax-test.network/ext/bc/C/rpc
networkId: 43113
type: EvmChain

View File

@ -0,0 +1,6 @@
id: gnosis
wormholeChainName: gnosis
mainnet: true
rpcUrl: https://rpc.gnosischain.com
networkId: 100
type: EvmChain

View File

@ -0,0 +1,6 @@
id: goerli
wormholeChainName: ethereum
mainnet: false
rpcUrl: https://rpc.goerli.eth.gateway.fm
networkId: 5
type: EvmChain

View File

@ -0,0 +1,6 @@
id: kava
wormholeChainName: kava
mainnet: true
rpcUrl: https://evm.kava.io
networkId: 2222
type: EvmChain

View File

@ -0,0 +1,6 @@
id: kava_testnet
wormholeChainName: kava
mainnet: false
rpcUrl: https://evm.testnet.kava.io
networkId: 2221
type: EvmChain

View File

@ -0,0 +1,6 @@
id: kcc
wormholeChainName: kcc
mainnet: true
rpcUrl: https://rpc-mainnet.kcc.network
networkId: 321
type: EvmChain

View File

@ -0,0 +1,6 @@
id: kcc_testnet
wormholeChainName: kcc
mainnet: false
rpcUrl: https://rpc-testnet.kcc.network
networkId: 322
type: EvmChain

View File

@ -0,0 +1,6 @@
id: linea
wormholeChainName: linea
mainnet: true
rpcUrl: https://linea.rpc.thirdweb.com
networkId: 59144
type: EvmChain

View File

@ -0,0 +1,6 @@
id: linea_goerli
wormholeChainName: linea
mainnet: false
rpcUrl: https://rpc.goerli.linea.build
networkId: 59140
type: EvmChain

View File

@ -0,0 +1,6 @@
id: mantle
wormholeChainName: mantle
mainnet: true
rpcUrl: https://rpc.mantle.xyz/
networkId: 5000
type: EvmChain

View File

@ -0,0 +1,6 @@
id: mantle_testnet
wormholeChainName: mantle
mainnet: false
rpcUrl: https://rpc.testnet.mantle.xyz/
networkId: 5001
type: EvmChain

View File

@ -0,0 +1,6 @@
id: meter
wormholeChainName: meter
mainnet: true
rpcUrl: https://rpc-meter.jellypool.xyz
networkId: 82
type: EvmChain

View File

@ -0,0 +1,6 @@
id: meter_testnet
wormholeChainName: meter
mainnet: false
rpcUrl: https://rpctest.meter.io
networkId: 83
type: EvmChain

View File

@ -0,0 +1,6 @@
id: mumbai
wormholeChainName: polygon
mainnet: false
rpcUrl: https://polygon-testnet-rpc.allthatnode.com:8545
networkId: 80001
type: EvmChain

View File

@ -0,0 +1,6 @@
id: neon
wormholeChainName: neon
mainnet: true
rpcUrl: https://neon-proxy-mainnet.solana.p2p.org
networkId: 245022934
type: EvmChain

View File

@ -0,0 +1,6 @@
id: neon_devnet
wormholeChainName: neon
mainnet: false
rpcUrl: https://devnet.neonevm.org
networkId: 245022926
type: EvmChain

View File

@ -0,0 +1,6 @@
id: oasis
wormholeChainName: oasis
mainnet: true
rpcUrl: https://emerald.oasis.dev/
networkId: 42262
type: EvmChain

View File

@ -0,0 +1,6 @@
id: optimism
wormholeChainName: optimism
mainnet: true
rpcUrl: https://rpc.ankr.com/optimism
networkId: 10
type: EvmChain

View File

@ -0,0 +1,6 @@
id: optimism_goerli
wormholeChainName: optimism
mainnet: false
rpcUrl: https://rpc.ankr.com/optimism_testnet
networkId: 420
type: EvmChain

View File

@ -0,0 +1,6 @@
id: polygon
wormholeChainName: polygon
mainnet: true
rpcUrl: https://polygon-rpc.com
networkId: 137
type: EvmChain

View File

@ -0,0 +1,6 @@
id: polygon_zkevm
wormholeChainName: polygon_zkevm
mainnet: true
rpcUrl: https://zkevm-rpc.com
networkId: 1101
type: EvmChain

View File

@ -0,0 +1,6 @@
id: polygon_zkevm_testnet
wormholeChainName: polygon_zkevm
mainnet: false
rpcUrl: https://rpc.public.zkevm-test.net/
networkId: 1442
type: EvmChain

View File

@ -0,0 +1,6 @@
id: wemix
wormholeChainName: wemix
mainnet: true
rpcUrl: https://api.wemix.com
networkId: 1111
type: EvmChain

View File

@ -0,0 +1,6 @@
id: wemix_testnet
wormholeChainName: wemix
mainnet: false
rpcUrl: https://api.test.wemix.com
networkId: 1112
type: EvmChain

View File

@ -0,0 +1,6 @@
id: zksync
wormholeChainName: zksync
mainnet: true
rpcUrl: https://zksync2-mainnet.zksync.io
networkId: 324
type: EvmChain

View File

@ -0,0 +1,6 @@
id: zksync_goerli
wormholeChainName: zksync
mainnet: false
rpcUrl: https://zksync2-testnet.zksync.dev
networkId: 280
type: EvmChain

View File

@ -1,3 +1,5 @@
id: sui_devnet
wormholeChainName: sui
mainnet: false
rpcUrl: https://fullnode.devnet.sui.io:443
type: SuiChain

View File

@ -1,3 +1,5 @@
id: sui_mainnet
wormholeChainName: sui
mainnet: true
rpcUrl: https://fullnode.mainnet.sui.io:443
type: SuiChain

View File

@ -1,3 +1,5 @@
id: sui_testnet
wormholeChainName: sui
mainnet: false
rpcUrl: https://fullnode.testnet.sui.io:443
type: SuiChain

View File

@ -0,0 +1,4 @@
chain: aptos_mainnet
stateId: "0x7e783b349d3e89cf5931af376ebeadbfab855b3fa239b7ada8f5a92fbea6b387"
wormholeStateId: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625"
type: AptosContract

View File

@ -1,3 +0,0 @@
chain: cronos
address: "0xe0d0e68297772dd5a1f1d99897c581e2082dba5b"
type: EVMContract

View File

@ -1,3 +0,0 @@
chain: cronos_testnet
address: "0xFF125F377F9F7631a05f4B01CeD32a6A2ab843C7"
type: EVMContract

View File

@ -0,0 +1,3 @@
chain: arbitrum
address: "0xff1a0f4744e8582DF1aE09D5611b887B6a12925C"
type: EvmContract

View File

@ -0,0 +1,3 @@
chain: arbitrum_testnet
address: "0x939C0e902FF5B3F7BA666Cc8F6aC75EE76d3f900"
type: EvmContract

View File

@ -0,0 +1,3 @@
chain: aurora
address: "0xF89C7b475821EC3fDC2dC8099032c05c6c0c9AB9"
type: EvmContract

View File

@ -0,0 +1,3 @@
chain: aurora_testnet
address: "0x4305FB66699C3B2702D4d05CF36551390A4c69C6"
type: EvmContract

View File

@ -0,0 +1,3 @@
chain: avalanche
address: "0x4305FB66699C3B2702D4d05CF36551390A4c69C6"
type: EvmContract

View File

@ -0,0 +1,3 @@
chain: base_goerli
address: "0x5955C1478F0dAD753C7E2B4dD1b4bC530C64749f"
type: EvmContract

View File

@ -0,0 +1,3 @@
chain: bnb
address: "0x4D7E825f80bDf85e913E0DD2A2D54927e9dE1594"
type: EvmContract

View File

@ -0,0 +1,3 @@
chain: bnb_testnet
address: "0xd7308b14BF4008e7C7196eC35610B1427C5702EA"
type: EvmContract

View File

@ -0,0 +1,3 @@
chain: canto
address: "0x98046Bd286715D3B0BC227Dd7a956b83D8978603"
type: EvmContract

View File

@ -0,0 +1,3 @@
chain: canto_testnet
address: "0xA2aa501b19aff244D90cc15a4Cf739D2725B5729"
type: EvmContract

View File

@ -0,0 +1,3 @@
chain: celo
address: "0xff1a0f4744e8582DF1aE09D5611b887B6a12925C"
type: EvmContract

View File

@ -0,0 +1,3 @@
chain: celo_alfajores_testnet
address: "0xff1a0f4744e8582DF1aE09D5611b887B6a12925C"
type: EvmContract

View File

@ -0,0 +1,3 @@
chain: chiado
address: "0xdDAf6D29b8bc81c1F0798a5e4c264ae89c16a72B"
type: EvmContract

View File

@ -0,0 +1,3 @@
chain: conflux_espace
address: "0xe9d69CdD6Fe41e7B621B4A688C5D1a68cB5c8ADc"
type: EvmContract

View File

@ -0,0 +1,3 @@
chain: conflux_espace_testnet
address: "0xA2aa501b19aff244D90cc15a4Cf739D2725B5729"
type: EvmContract

View File

@ -0,0 +1,3 @@
chain: cronos
address: "0xE0d0e68297772Dd5a1f1D99897c581E2082dbA5B"
type: EvmContract

View File

@ -0,0 +1,3 @@
chain: cronos_testnet
address: "0xBAEA4A1A2Eaa4E9bb78f2303C213Da152933170E"
type: EvmContract

View File

@ -0,0 +1,3 @@
chain: ethereum
address: "0x4305FB66699C3B2702D4d05CF36551390A4c69C6"
type: EvmContract

View File

@ -0,0 +1,3 @@
chain: evmos
address: "0x354bF866A4B006C9AF9d9e06d9364217A8616E12"
type: EvmContract

View File

@ -0,0 +1,3 @@
chain: evmos_testnet
address: "0x354bF866A4B006C9AF9d9e06d9364217A8616E12"
type: EvmContract

View File

@ -0,0 +1,3 @@
chain: fantom
address: "0xff1a0f4744e8582DF1aE09D5611b887B6a12925C"
type: EvmContract

View File

@ -0,0 +1,3 @@
chain: fantom_testnet
address: "0xff1a0f4744e8582DF1aE09D5611b887B6a12925C"
type: EvmContract

View File

@ -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