Initial version of contract manager sdk (#943)
* Initial version of governance sdk * Add more functionality to Sui contract manager and migrate variable naming to camelCase * Refactor sui functions * Add prettier * Add SuiAuthorizeUpgradeContractInstruction for governance * Update cosmwasm deploy tools entry point and expose some classes * Remove console.logs from CosmWasm * Refactor storage logic and add sui docs * Use relative path for default path of store * More documentation and minor fixes * Rename package * Add EVM classes * Implement getters for data sources * Use Google naming convention for abbreviations More info here: https://google.github.io/styleguide/tsguide.html#identifiers-abbreviations * Change package license * More comments and documentation * Store code proxy function in CosmWasm
This commit is contained in:
parent
a77ee78d13
commit
66e5f186b2
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"name": "@pythnetwork/pyth-contract-manager",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Set of tools to manage pyth contracts",
|
||||||
|
"private": true,
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"shell": "ts-node ./src/shell.ts"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/pyth-network/pyth-crosschain.git"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@pythnetwork/cosmwasm-deploy-tools": "*",
|
||||||
|
"@pythnetwork/price-service-client": "*",
|
||||||
|
"@pythnetwork/xc-governance-sdk": "*",
|
||||||
|
"@certusone/wormhole-sdk": "^0.9.8",
|
||||||
|
"ts-node": "^10.9.1",
|
||||||
|
"typescript": "^4.9.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"prettier": "^2.6.2"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
import { DataSource, HexString32Bytes } from "@pythnetwork/xc-governance-sdk";
|
||||||
|
|
||||||
|
export abstract class Storable {
|
||||||
|
/**
|
||||||
|
* Returns the unique identifier for this object
|
||||||
|
*/
|
||||||
|
abstract getId(): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the type of this object. This is used to reconstruct the object and should match
|
||||||
|
* the static field type in the class responsible for constructing this object.
|
||||||
|
*/
|
||||||
|
abstract getType(): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a JSON representation of this object. It should be possible to
|
||||||
|
* reconstruct the object from the JSON using the fromJson method.
|
||||||
|
*/
|
||||||
|
abstract toJson(): any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class Contract extends Storable {
|
||||||
|
/**
|
||||||
|
* Returns the time period in seconds that stale data is considered valid for.
|
||||||
|
*/
|
||||||
|
abstract getValidTimePeriod(): Promise<number>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of data sources that this contract accepts price feed messages from
|
||||||
|
*/
|
||||||
|
abstract getDataSources(): Promise<DataSource[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the single data source that this contract accepts governance messages from
|
||||||
|
*/
|
||||||
|
abstract getGovernanceDataSource(): Promise<DataSource>;
|
||||||
|
}
|
|
@ -0,0 +1,107 @@
|
||||||
|
import { readdirSync, readFileSync, writeFileSync } from "fs";
|
||||||
|
import { Storable } from "./base";
|
||||||
|
|
||||||
|
export abstract class Chain extends Storable {
|
||||||
|
protected constructor(public id: string) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
getId(): string {
|
||||||
|
return this.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CosmWasmChain extends Chain {
|
||||||
|
static type: string = "CosmWasmChain";
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
id: string,
|
||||||
|
public querierEndpoint: string,
|
||||||
|
public executorEndpoint: string,
|
||||||
|
public gasPrice: string,
|
||||||
|
public prefix: string,
|
||||||
|
public feeDenom: string
|
||||||
|
) {
|
||||||
|
super(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(parsed: any): CosmWasmChain {
|
||||||
|
if (parsed.type !== CosmWasmChain.type) throw new Error("Invalid type");
|
||||||
|
return new CosmWasmChain(
|
||||||
|
parsed.id,
|
||||||
|
parsed.querierEndpoint,
|
||||||
|
parsed.executorEndpoint,
|
||||||
|
parsed.gasPrice,
|
||||||
|
parsed.prefix,
|
||||||
|
parsed.feeDenom
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson(): any {
|
||||||
|
return {
|
||||||
|
querierEndpoint: this.querierEndpoint,
|
||||||
|
executorEndpoint: this.executorEndpoint,
|
||||||
|
id: this.id,
|
||||||
|
gasPrice: this.gasPrice,
|
||||||
|
prefix: this.prefix,
|
||||||
|
feeDenom: this.feeDenom,
|
||||||
|
type: CosmWasmChain.type,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getType(): string {
|
||||||
|
return CosmWasmChain.type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SuiChain extends Chain {
|
||||||
|
static type: string = "SuiChain";
|
||||||
|
|
||||||
|
constructor(id: string, public rpcUrl: string) {
|
||||||
|
super(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(parsed: any): SuiChain {
|
||||||
|
if (parsed.type !== SuiChain.type) throw new Error("Invalid type");
|
||||||
|
return new SuiChain(parsed.id, parsed.rpcUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson(): any {
|
||||||
|
return {
|
||||||
|
id: this.id,
|
||||||
|
rpcUrl: this.rpcUrl,
|
||||||
|
type: SuiChain.type,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getType(): string {
|
||||||
|
return SuiChain.type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class EVMChain extends Chain {
|
||||||
|
static type: string = "EVMChain";
|
||||||
|
|
||||||
|
constructor(id: string, public rpcUrl: string) {
|
||||||
|
super(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(parsed: any): SuiChain {
|
||||||
|
if (parsed.type !== EVMChain.type) throw new Error("Invalid type");
|
||||||
|
return new EVMChain(parsed.id, parsed.rpcUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson(): any {
|
||||||
|
return {
|
||||||
|
id: this.id,
|
||||||
|
rpcUrl: this.rpcUrl,
|
||||||
|
type: EVMChain.type,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getType(): string {
|
||||||
|
return EVMChain.type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Chains: Record<string, Chain> = {};
|
|
@ -0,0 +1,344 @@
|
||||||
|
import { Chains, CosmWasmChain } from "./chains";
|
||||||
|
import { readFileSync } from "fs";
|
||||||
|
import { getPythConfig } from "@pythnetwork/cosmwasm-deploy-tools/lib/configs";
|
||||||
|
import {
|
||||||
|
CHAINS,
|
||||||
|
DataSource,
|
||||||
|
HexString32Bytes,
|
||||||
|
SetFeeInstruction,
|
||||||
|
} from "@pythnetwork/xc-governance-sdk";
|
||||||
|
import { DeploymentType } from "@pythnetwork/cosmwasm-deploy-tools/lib/helper";
|
||||||
|
import {
|
||||||
|
CosmwasmExecutor,
|
||||||
|
PythWrapperExecutor,
|
||||||
|
PythWrapperQuerier,
|
||||||
|
} from "@pythnetwork/cosmwasm-deploy-tools";
|
||||||
|
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";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Variables here need to be snake case to match the on-chain contract configs
|
||||||
|
*/
|
||||||
|
namespace CosmWasmContract {
|
||||||
|
export interface WormholeSource {
|
||||||
|
emitter: string;
|
||||||
|
chain_id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DeploymentConfig {
|
||||||
|
data_sources: WormholeSource[];
|
||||||
|
governance_source: WormholeSource;
|
||||||
|
wormhole_contract: string;
|
||||||
|
governance_source_index: number;
|
||||||
|
governance_sequence_number: number;
|
||||||
|
chain_id: number;
|
||||||
|
valid_time_period_secs: number;
|
||||||
|
fee: { amount: string; denom: string };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CosmWasmContract extends Contract {
|
||||||
|
async getDataSources(): Promise<DataSource[]> {
|
||||||
|
const config = await this.getConfig();
|
||||||
|
return config.config_v1.data_sources.map(({ emitter, chain_id }: any) => {
|
||||||
|
return new DataSource(
|
||||||
|
Number(chain_id),
|
||||||
|
new HexString32Bytes(Buffer.from(emitter, "base64").toString("hex"))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getGovernanceDataSource(): Promise<DataSource> {
|
||||||
|
const config = await this.getConfig();
|
||||||
|
const { emitter: emitterAddress, chain_id: chainId } =
|
||||||
|
config.config_v1.governance_source;
|
||||||
|
return new DataSource(
|
||||||
|
Number(chainId),
|
||||||
|
new HexString32Bytes(
|
||||||
|
Buffer.from(emitterAddress, "base64").toString("hex")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static type = "CosmWasmContract";
|
||||||
|
|
||||||
|
constructor(public chain: CosmWasmChain, public address: string) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(parsed: any): CosmWasmContract {
|
||||||
|
if (parsed.type !== CosmWasmContract.type) throw new Error("Invalid type");
|
||||||
|
if (!Chains[parsed.chain])
|
||||||
|
throw new Error(`Chain ${parsed.chain} not found`);
|
||||||
|
return new CosmWasmContract(
|
||||||
|
Chains[parsed.chain] as CosmWasmChain,
|
||||||
|
parsed.address
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getType(): string {
|
||||||
|
return CosmWasmContract.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO : make deploymentType enum stable | edge
|
||||||
|
static getDeploymentConfig(
|
||||||
|
chain: CosmWasmChain,
|
||||||
|
deploymentType: string,
|
||||||
|
wormholeContract: string
|
||||||
|
): CosmWasmContract.DeploymentConfig {
|
||||||
|
return getPythConfig({
|
||||||
|
feeDenom: chain.feeDenom,
|
||||||
|
wormholeChainId: CHAINS[chain.getId() as keyof typeof CHAINS],
|
||||||
|
wormholeContract,
|
||||||
|
deploymentType: deploymentType as DeploymentType,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 wasmPath path in your local filesystem to the wasm artifact
|
||||||
|
*/
|
||||||
|
static async storeCode(
|
||||||
|
chain: CosmWasmChain,
|
||||||
|
mnemonic: string,
|
||||||
|
wasmPath: string
|
||||||
|
) {
|
||||||
|
const contractBytes = readFileSync(wasmPath);
|
||||||
|
let executor = this.getExecutor(chain, mnemonic);
|
||||||
|
return executor.storeCode({ contractBytes });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deploys a new contract to the specified chain using the uploaded wasm code codeId
|
||||||
|
* @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
|
||||||
|
*/
|
||||||
|
static async initialize(
|
||||||
|
chain: CosmWasmChain,
|
||||||
|
codeId: number,
|
||||||
|
config: CosmWasmContract.DeploymentConfig,
|
||||||
|
mnemonic: string
|
||||||
|
): Promise<CosmWasmContract> {
|
||||||
|
let executor = this.getExecutor(chain, mnemonic);
|
||||||
|
let result = await executor.instantiateContract({
|
||||||
|
codeId: codeId,
|
||||||
|
instMsg: config,
|
||||||
|
label: "pyth",
|
||||||
|
});
|
||||||
|
await executor.updateContractAdmin({
|
||||||
|
newAdminAddr: result.contractAddr,
|
||||||
|
contractAddr: result.contractAddr,
|
||||||
|
});
|
||||||
|
return new CosmWasmContract(chain, result.contractAddr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uploads the wasm code and initializes a new contract to the specified chain.
|
||||||
|
* Use this method if you are deploying to a new chain, or you want a fresh contract in
|
||||||
|
* a testnet environment. Uses the default deployment configurations for governance, data sources,
|
||||||
|
* valid time period, etc. You can manually run the storeCode and initialize methods if you want
|
||||||
|
* more control over the deployment process.
|
||||||
|
* @param chain
|
||||||
|
* @param wormholeContract
|
||||||
|
* @param mnemonic
|
||||||
|
* @param wasmPath
|
||||||
|
*/
|
||||||
|
static async deploy(
|
||||||
|
chain: CosmWasmChain,
|
||||||
|
wormholeContract: string,
|
||||||
|
mnemonic: 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static getExecutor(chain: CosmWasmChain, mnemonic: string) {
|
||||||
|
// TODO: logic for injective
|
||||||
|
return new CosmwasmExecutor(
|
||||||
|
chain.executorEndpoint,
|
||||||
|
mnemonic,
|
||||||
|
chain.prefix,
|
||||||
|
chain.gasPrice + chain.feeDenom
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getId(): string {
|
||||||
|
return `${this.chain.getId()}_${this.address}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
chain: this.chain.id,
|
||||||
|
address: this.address,
|
||||||
|
type: CosmWasmContract.type,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async getQuerier(): Promise<PythWrapperQuerier> {
|
||||||
|
const chainQuerier = await CosmwasmQuerier.connect(
|
||||||
|
this.chain.querierEndpoint
|
||||||
|
);
|
||||||
|
const pythQuerier = new PythWrapperQuerier(chainQuerier);
|
||||||
|
return pythQuerier;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCodeId(): Promise<number> {
|
||||||
|
let result = await this.getWasmContractInfo();
|
||||||
|
return result.codeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getWasmContractInfo(): Promise<ContractInfoResponse> {
|
||||||
|
const chainQuerier = await CosmwasmQuerier.connect(
|
||||||
|
this.chain.querierEndpoint
|
||||||
|
);
|
||||||
|
return chainQuerier.getContractInfo({ contractAddr: this.address });
|
||||||
|
}
|
||||||
|
|
||||||
|
async getConfig() {
|
||||||
|
const chainQuerier = await CosmwasmQuerier.connect(
|
||||||
|
this.chain.querierEndpoint
|
||||||
|
);
|
||||||
|
let allStates = (await chainQuerier.getAllContractState({
|
||||||
|
contractAddr: this.address,
|
||||||
|
})) as any;
|
||||||
|
let config = {
|
||||||
|
config_v1: JSON.parse(allStates["\x00\tconfig_v1"]),
|
||||||
|
contract_version: JSON.parse(allStates["\x00\x10contract_version"]),
|
||||||
|
};
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: function for uploading the code and getting the code id
|
||||||
|
// TODO: function for upgrading the contract
|
||||||
|
// TODO: Cleanup and more strict linter to convert let to const
|
||||||
|
|
||||||
|
async getPriceFeed(feedId: string): Promise<any> {
|
||||||
|
let querier = await this.getQuerier();
|
||||||
|
return querier.getPriceFeed(this.address, feedId);
|
||||||
|
}
|
||||||
|
|
||||||
|
equalDataSources(
|
||||||
|
dataSources1: CosmWasmContract.WormholeSource[],
|
||||||
|
dataSources2: CosmWasmContract.WormholeSource[]
|
||||||
|
): boolean {
|
||||||
|
if (dataSources1.length !== dataSources2.length) return false;
|
||||||
|
for (let i = 0; i < dataSources1.length; i++) {
|
||||||
|
let found = false;
|
||||||
|
for (let j = 0; j < dataSources2.length; j++) {
|
||||||
|
if (
|
||||||
|
dataSources1[i].emitter === dataSources2[j].emitter &&
|
||||||
|
dataSources1[i].chain_id === dataSources2[j].chain_id
|
||||||
|
) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getDeploymentType(): Promise<string> {
|
||||||
|
let config = await this.getConfig();
|
||||||
|
let wormholeContract = config.config_v1.wormhole_contract;
|
||||||
|
let stableConfig = getPythConfig({
|
||||||
|
feeDenom: this.chain.feeDenom,
|
||||||
|
wormholeChainId: CHAINS[this.chain.getId() as keyof typeof CHAINS],
|
||||||
|
wormholeContract,
|
||||||
|
deploymentType: "stable",
|
||||||
|
});
|
||||||
|
let edgeConfig = getPythConfig({
|
||||||
|
feeDenom: this.chain.feeDenom,
|
||||||
|
wormholeChainId: CHAINS[this.chain.getId() as keyof typeof CHAINS],
|
||||||
|
wormholeContract,
|
||||||
|
deploymentType: "edge",
|
||||||
|
});
|
||||||
|
if (
|
||||||
|
this.equalDataSources(
|
||||||
|
config.config_v1.data_sources,
|
||||||
|
stableConfig.data_sources
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return "stable";
|
||||||
|
else if (
|
||||||
|
this.equalDataSources(
|
||||||
|
config.config_v1.data_sources,
|
||||||
|
edgeConfig.data_sources
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return "edge";
|
||||||
|
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
|
||||||
|
);
|
||||||
|
let pythExecutor = new PythWrapperExecutor(executor);
|
||||||
|
return pythExecutor.executeUpdatePriceFeeds({
|
||||||
|
contractAddr: this.address,
|
||||||
|
vaas,
|
||||||
|
fund,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async executeGovernanceInstruction(mnemonic: string, vaa: string) {
|
||||||
|
let executor = new CosmwasmExecutor(
|
||||||
|
this.chain.executorEndpoint,
|
||||||
|
mnemonic,
|
||||||
|
this.chain.prefix,
|
||||||
|
this.chain.gasPrice + this.chain.feeDenom
|
||||||
|
);
|
||||||
|
let pythExecutor = new PythWrapperExecutor(executor);
|
||||||
|
return pythExecutor.executeGovernanceInstruction({
|
||||||
|
contractAddr: this.address,
|
||||||
|
vaa,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getUpdateFee(msgs: string[]): Promise<any> {
|
||||||
|
let querier = await this.getQuerier();
|
||||||
|
return querier.getUpdateFee(this.address, msgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
getSetUpdateFeePayload(fee: number): Buffer {
|
||||||
|
return new SetFeeInstruction(
|
||||||
|
CHAINS[this.chain.getId() as keyof typeof CHAINS],
|
||||||
|
BigInt(fee),
|
||||||
|
BigInt(0)
|
||||||
|
).serialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getValidTimePeriod() {
|
||||||
|
let client = await CosmWasmClient.connect(this.chain.querierEndpoint);
|
||||||
|
let result = await client.queryContractSmart(
|
||||||
|
this.address,
|
||||||
|
"get_valid_time_period"
|
||||||
|
);
|
||||||
|
return Number(result.secs + result.nanos * 1e-9);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,273 @@
|
||||||
|
import { readFileSync } from "fs";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Connection,
|
||||||
|
Keypair,
|
||||||
|
PublicKey,
|
||||||
|
SystemProgram,
|
||||||
|
SYSVAR_CLOCK_PUBKEY,
|
||||||
|
SYSVAR_RENT_PUBKEY,
|
||||||
|
Transaction,
|
||||||
|
TransactionInstruction,
|
||||||
|
} from "@solana/web3.js";
|
||||||
|
|
||||||
|
import { BN } from "bn.js";
|
||||||
|
import { getPythClusterApiUrl } from "@pythnetwork/client/lib/cluster";
|
||||||
|
import SquadsMesh, { getTxPDA } from "@sqds/mesh";
|
||||||
|
import { AnchorProvider, Wallet } from "@coral-xyz/anchor/dist/cjs/provider";
|
||||||
|
import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet";
|
||||||
|
import { WORMHOLE_ADDRESS, WORMHOLE_API_ENDPOINT } from "xc_admin_common";
|
||||||
|
import {
|
||||||
|
createWormholeProgramInterface,
|
||||||
|
deriveEmitterSequenceKey,
|
||||||
|
deriveFeeCollectorKey,
|
||||||
|
deriveWormholeBridgeDataKey,
|
||||||
|
} from "@certusone/wormhole-sdk/lib/cjs/solana/wormhole";
|
||||||
|
import { Contract, Storable } from "./base";
|
||||||
|
|
||||||
|
export const Contracts: Record<string, Contract> = {};
|
||||||
|
|
||||||
|
export class SubmittedWormholeMessage {
|
||||||
|
constructor(
|
||||||
|
public emitter: PublicKey,
|
||||||
|
public sequenceNumber: number,
|
||||||
|
public cluster: string
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to fetch the VAA from the wormhole bridge API waiting for a certain amount of time
|
||||||
|
* before giving up and throwing an error
|
||||||
|
* @param waitingSeconds how long to wait before giving up
|
||||||
|
*/
|
||||||
|
async fetchVaa(waitingSeconds: number = 1): Promise<Buffer> {
|
||||||
|
let rpcUrl =
|
||||||
|
WORMHOLE_API_ENDPOINT[this.cluster as keyof typeof WORMHOLE_API_ENDPOINT];
|
||||||
|
|
||||||
|
let startTime = Date.now();
|
||||||
|
while (Date.now() - startTime < waitingSeconds * 1000) {
|
||||||
|
const response = await fetch(
|
||||||
|
`${rpcUrl}/v1/signed_vaa/1/${this.emitter.toBuffer().toString("hex")}/${
|
||||||
|
this.sequenceNumber
|
||||||
|
}`
|
||||||
|
);
|
||||||
|
if (response.status === 404) {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const { vaaBytes } = await response.json();
|
||||||
|
return Buffer.from(vaaBytes, "base64");
|
||||||
|
}
|
||||||
|
throw new Error("VAA not found, maybe too soon to fetch?");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple emitter that can send messages to the wormhole bridge
|
||||||
|
* This can be used instead of multisig as a simple way to send messages
|
||||||
|
* and debug contracts deployed on testing networks
|
||||||
|
* You need to set your pyth contract data source / governance source address to this emitter
|
||||||
|
*/
|
||||||
|
export class WormholeEmitter {
|
||||||
|
cluster: string;
|
||||||
|
wallet: Wallet;
|
||||||
|
|
||||||
|
constructor(cluster: string, wallet: Wallet) {
|
||||||
|
this.cluster = cluster;
|
||||||
|
this.wallet = wallet;
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendMessage(payload: Buffer) {
|
||||||
|
const provider = new AnchorProvider(
|
||||||
|
new Connection(getPythClusterApiUrl(this.cluster as any), "confirmed"),
|
||||||
|
this.wallet,
|
||||||
|
{
|
||||||
|
commitment: "confirmed",
|
||||||
|
preflightCommitment: "confirmed",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
let wormholeAddress =
|
||||||
|
WORMHOLE_ADDRESS[this.cluster as keyof typeof WORMHOLE_ADDRESS]!;
|
||||||
|
let kp = Keypair.generate();
|
||||||
|
let feeCollector = deriveFeeCollectorKey(wormholeAddress);
|
||||||
|
let emitter = this.wallet.publicKey;
|
||||||
|
let accounts = {
|
||||||
|
bridge: deriveWormholeBridgeDataKey(wormholeAddress),
|
||||||
|
message: kp.publicKey,
|
||||||
|
emitter: emitter,
|
||||||
|
sequence: deriveEmitterSequenceKey(emitter, wormholeAddress),
|
||||||
|
payer: emitter,
|
||||||
|
feeCollector,
|
||||||
|
clock: SYSVAR_CLOCK_PUBKEY,
|
||||||
|
rent: SYSVAR_RENT_PUBKEY,
|
||||||
|
systemProgram: SystemProgram.programId,
|
||||||
|
};
|
||||||
|
const wormholeProgram = createWormholeProgramInterface(
|
||||||
|
wormholeAddress,
|
||||||
|
provider
|
||||||
|
);
|
||||||
|
const transaction = new Transaction();
|
||||||
|
transaction.add(
|
||||||
|
SystemProgram.transfer({
|
||||||
|
fromPubkey: emitter,
|
||||||
|
toPubkey: feeCollector,
|
||||||
|
lamports: 1000,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
transaction.add(
|
||||||
|
await wormholeProgram.methods
|
||||||
|
.postMessage(0, payload, 0)
|
||||||
|
.accounts(accounts)
|
||||||
|
.instruction()
|
||||||
|
);
|
||||||
|
const txSig = await provider.sendAndConfirm(transaction, [kp]);
|
||||||
|
const txDetails = await provider.connection.getParsedTransaction(txSig);
|
||||||
|
const sequenceLogPrefix = "Sequence: ";
|
||||||
|
const txLog = txDetails?.meta?.logMessages?.find((s) =>
|
||||||
|
s.includes(sequenceLogPrefix)
|
||||||
|
);
|
||||||
|
|
||||||
|
const sequenceNumber = Number(
|
||||||
|
txLog?.substring(
|
||||||
|
txLog.indexOf(sequenceLogPrefix) + sequenceLogPrefix.length
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return new SubmittedWormholeMessage(emitter, sequenceNumber, this.cluster);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Vault extends Storable {
|
||||||
|
static type: string = "vault";
|
||||||
|
key: PublicKey;
|
||||||
|
squad?: SquadsMesh;
|
||||||
|
cluster: string;
|
||||||
|
|
||||||
|
constructor(key: string, cluster: string) {
|
||||||
|
super();
|
||||||
|
this.key = new PublicKey(key);
|
||||||
|
this.cluster = cluster;
|
||||||
|
}
|
||||||
|
|
||||||
|
getType(): string {
|
||||||
|
return Vault.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
static from(path: string): Vault {
|
||||||
|
let parsed = JSON.parse(readFileSync(path, "utf-8"));
|
||||||
|
if (parsed.type !== Vault.type) throw new Error("Invalid type");
|
||||||
|
return new Vault(parsed.key, parsed.cluster);
|
||||||
|
}
|
||||||
|
|
||||||
|
getId(): string {
|
||||||
|
return `${this.cluster}_${this.key.toString()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson(): any {
|
||||||
|
return {
|
||||||
|
key: this.key.toString(),
|
||||||
|
cluster: this.cluster,
|
||||||
|
type: Vault.type,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public connect(wallet: Wallet): void {
|
||||||
|
this.squad = SquadsMesh.endpoint(
|
||||||
|
getPythClusterApiUrl(this.cluster as any), // TODO Fix any
|
||||||
|
wallet
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async createProposalIx(
|
||||||
|
proposalIndex: number
|
||||||
|
): Promise<[TransactionInstruction, PublicKey]> {
|
||||||
|
const squad = this.getSquadOrThrow();
|
||||||
|
const msAccount = await squad.getMultisig(this.key);
|
||||||
|
|
||||||
|
const ix = await squad.buildCreateTransaction(
|
||||||
|
msAccount.publicKey,
|
||||||
|
msAccount.authorityIndex,
|
||||||
|
proposalIndex
|
||||||
|
);
|
||||||
|
|
||||||
|
const newProposalAddress = getTxPDA(
|
||||||
|
this.key,
|
||||||
|
new BN(proposalIndex),
|
||||||
|
squad.multisigProgramId
|
||||||
|
)[0];
|
||||||
|
|
||||||
|
return [ix, newProposalAddress];
|
||||||
|
}
|
||||||
|
|
||||||
|
public async activateProposalIx(
|
||||||
|
proposalAddress: PublicKey
|
||||||
|
): Promise<TransactionInstruction> {
|
||||||
|
const squad = this.getSquadOrThrow();
|
||||||
|
return await squad.buildActivateTransaction(this.key, proposalAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async approveProposalIx(
|
||||||
|
proposalAddress: PublicKey
|
||||||
|
): Promise<TransactionInstruction> {
|
||||||
|
const squad = this.getSquadOrThrow();
|
||||||
|
return await squad.buildApproveTransaction(this.key, proposalAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
getSquadOrThrow(): SquadsMesh {
|
||||||
|
if (!this.squad) throw new Error("Please connect a wallet to the vault");
|
||||||
|
return this.squad;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async proposeWormholeMessage(payload: Buffer): Promise<any> {
|
||||||
|
const squad = this.getSquadOrThrow();
|
||||||
|
const msAccount = await squad.getMultisig(this.key);
|
||||||
|
|
||||||
|
let ixToSend: TransactionInstruction[] = [];
|
||||||
|
const [proposalIx, newProposalAddress] = await this.createProposalIx(
|
||||||
|
msAccount.transactionIndex + 1
|
||||||
|
);
|
||||||
|
|
||||||
|
const proposalIndex = msAccount.transactionIndex + 1;
|
||||||
|
ixToSend.push(proposalIx);
|
||||||
|
return ixToSend;
|
||||||
|
// const instructionToPropose = await getPostMessageInstruction(
|
||||||
|
// squad,
|
||||||
|
// this.key,
|
||||||
|
// newProposalAddress,
|
||||||
|
// 1,
|
||||||
|
// this.wormholeAddress()!,
|
||||||
|
// payload
|
||||||
|
// );
|
||||||
|
// ixToSend.push(
|
||||||
|
// await squad.buildAddInstruction(
|
||||||
|
// this.key,
|
||||||
|
// newProposalAddress,
|
||||||
|
// instructionToPropose.instruction,
|
||||||
|
// 1,
|
||||||
|
// instructionToPropose.authorityIndex,
|
||||||
|
// instructionToPropose.authorityBump,
|
||||||
|
// instructionToPropose.authorityType
|
||||||
|
// )
|
||||||
|
// );
|
||||||
|
// ixToSend.push(await this.activateProposalIx(newProposalAddress));
|
||||||
|
// ixToSend.push(await this.approveProposalIx(newProposalAddress));
|
||||||
|
|
||||||
|
// const txToSend = batchIntoTransactions(ixToSend);
|
||||||
|
// for (let i = 0; i < txToSend.length; i += SIZE_OF_SIGNED_BATCH) {
|
||||||
|
// await this.getAnchorProvider().sendAll(
|
||||||
|
// txToSend.slice(i, i + SIZE_OF_SIGNED_BATCH).map((tx) => {
|
||||||
|
// return { tx, signers: [] };
|
||||||
|
// })
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// return newProposalAddress;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Vaults: Record<string, Vault> = {};
|
||||||
|
|
||||||
|
export async function loadHotWallet(wallet: string): Promise<Wallet> {
|
||||||
|
return new NodeWallet(
|
||||||
|
Keypair.fromSecretKey(
|
||||||
|
Uint8Array.from(JSON.parse(readFileSync(wallet, "ascii")))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,134 @@
|
||||||
|
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 { Chains, EVMChain } from "./chains";
|
||||||
|
import { DataSource, HexString32Bytes } from "@pythnetwork/xc-governance-sdk";
|
||||||
|
|
||||||
|
export class EVMContract extends Contract {
|
||||||
|
static type = "EVMContract";
|
||||||
|
|
||||||
|
constructor(public chain: EVMChain, public address: string) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(parsed: any): EVMContract {
|
||||||
|
if (parsed.type !== EVMContract.type) throw new Error("Invalid type");
|
||||||
|
if (!Chains[parsed.chain])
|
||||||
|
throw new Error(`Chain ${parsed.chain} not found`);
|
||||||
|
return new EVMContract(Chains[parsed.chain] as EVMChain, 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(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
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: [],
|
||||||
|
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,
|
||||||
|
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 getDataSources(): Promise<DataSource[]> {
|
||||||
|
const pythContract = this.getContract();
|
||||||
|
const result = await pythContract.methods.validDataSources().call();
|
||||||
|
return result.map(({ chainId, emitterAddress }: any) => {
|
||||||
|
return new DataSource(
|
||||||
|
Number(chainId),
|
||||||
|
new HexString32Bytes(emitterAddress)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getGovernanceDataSource(): Promise<DataSource> {
|
||||||
|
const pythContract = this.getContract();
|
||||||
|
const [chainId, emitterAddress] = await pythContract.methods
|
||||||
|
.governanceDataSource()
|
||||||
|
.call();
|
||||||
|
return new DataSource(
|
||||||
|
Number(chainId),
|
||||||
|
new HexString32Bytes(emitterAddress)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
chain: this.chain.id,
|
||||||
|
address: this.address,
|
||||||
|
type: EVMContract.type,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
import * as tsNode from "ts-node";
|
||||||
|
|
||||||
|
const repl = tsNode.createRepl();
|
||||||
|
const service = tsNode.create({ ...repl.evalAwarePartialHost });
|
||||||
|
repl.setService(service);
|
||||||
|
repl.start();
|
||||||
|
repl.evalCode(
|
||||||
|
"import { Contracts, Vaults, loadHotWallet } from './src/entities';" +
|
||||||
|
"import { Chains,SuiChain,CosmWasmChain } from './src/chains';" +
|
||||||
|
"import { SuiContract } from './src/sui';" +
|
||||||
|
"import { CosmWasmContract } from './src/cosmwasm';" +
|
||||||
|
"import { DefaultStore } from './src/store';" +
|
||||||
|
"DefaultStore"
|
||||||
|
);
|
||||||
|
|
||||||
|
// import * as repl from 'node:repl';
|
||||||
|
// import { CosmWasmChain, Chains, ChainContracts } from './entities';
|
||||||
|
// // import { CHAINS_NETWORK_CONFIG } from './chains-manager/chains';
|
||||||
|
|
||||||
|
// const replServer = repl.start('Pyth shell> ')
|
||||||
|
// // const mnemonic = "salon myth guide analyst umbrella load arm first roast pelican stuff satoshi";
|
||||||
|
|
||||||
|
// replServer.context.CosmWasmChain = CosmWasmChain;
|
||||||
|
// replServer.context.Chains = Chains;
|
||||||
|
// replServer.context.ChainContracts = ChainContracts;
|
|
@ -0,0 +1,105 @@
|
||||||
|
import { Chain, CosmWasmChain, SuiChain, Chains, EVMChain } from "./chains";
|
||||||
|
import { CosmWasmContract } from "./cosmwasm";
|
||||||
|
import { SuiContract } from "./sui";
|
||||||
|
import { Contract } from "./base";
|
||||||
|
import {
|
||||||
|
readdirSync,
|
||||||
|
readFileSync,
|
||||||
|
writeFileSync,
|
||||||
|
mkdirSync,
|
||||||
|
existsSync,
|
||||||
|
statSync,
|
||||||
|
} from "fs";
|
||||||
|
import { Contracts } from "./entities";
|
||||||
|
import { EVMContract } from "./evm";
|
||||||
|
|
||||||
|
class Store {
|
||||||
|
static Chains: Record<string, Chain> = {};
|
||||||
|
static Contracts: Record<string, CosmWasmContract | SuiContract> = {};
|
||||||
|
|
||||||
|
constructor(public path: string) {
|
||||||
|
this.loadAllChains();
|
||||||
|
this.loadAllContracts();
|
||||||
|
}
|
||||||
|
|
||||||
|
save(obj: any) {
|
||||||
|
let dir, file, content;
|
||||||
|
if (obj instanceof Contract) {
|
||||||
|
let contract = obj;
|
||||||
|
dir = `${this.path}/contracts/${contract.getType()}`;
|
||||||
|
file = contract.getId();
|
||||||
|
content = contract.toJson();
|
||||||
|
} else if (obj instanceof Chain) {
|
||||||
|
let chain = obj;
|
||||||
|
dir = `${this.path}/chains/${chain.getType()}`;
|
||||||
|
file = chain.getId();
|
||||||
|
content = chain.toJson();
|
||||||
|
} else {
|
||||||
|
throw new Error("Invalid type");
|
||||||
|
}
|
||||||
|
if (!existsSync(dir)) {
|
||||||
|
mkdirSync(dir, { recursive: true });
|
||||||
|
}
|
||||||
|
writeFileSync(
|
||||||
|
`${dir}/${file}.json`,
|
||||||
|
JSON.stringify(content, undefined, 2) + "\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getJSONFiles(path: string) {
|
||||||
|
const walk = function (dir: string) {
|
||||||
|
let results: string[] = [];
|
||||||
|
const list = readdirSync(dir);
|
||||||
|
list.forEach(function (file) {
|
||||||
|
file = dir + "/" + file;
|
||||||
|
const stat = statSync(file);
|
||||||
|
if (stat && stat.isDirectory()) {
|
||||||
|
// Recurse into a subdirectory
|
||||||
|
results = results.concat(walk(file));
|
||||||
|
} else {
|
||||||
|
// Is a file
|
||||||
|
results.push(file);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return results;
|
||||||
|
};
|
||||||
|
return walk(path).filter((file) => file.endsWith(".json"));
|
||||||
|
}
|
||||||
|
|
||||||
|
loadAllChains() {
|
||||||
|
let allChainClasses = {
|
||||||
|
[CosmWasmChain.type]: CosmWasmChain,
|
||||||
|
[SuiChain.type]: SuiChain,
|
||||||
|
[EVMChain.type]: EVMChain,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.getJSONFiles(`${this.path}/chains/`).forEach((jsonFile) => {
|
||||||
|
let parsed = JSON.parse(readFileSync(jsonFile, "utf-8"));
|
||||||
|
if (allChainClasses[parsed.type] === undefined) return;
|
||||||
|
let chain = allChainClasses[parsed.type].fromJson(parsed);
|
||||||
|
if (Chains[chain.getId()])
|
||||||
|
throw new Error(`Multiple chains with id ${chain.getId()} found`);
|
||||||
|
Chains[chain.getId()] = chain;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loadAllContracts() {
|
||||||
|
let allContractClasses = {
|
||||||
|
[CosmWasmContract.type]: CosmWasmContract,
|
||||||
|
[SuiContract.type]: SuiContract,
|
||||||
|
[EVMContract.type]: EVMContract,
|
||||||
|
};
|
||||||
|
this.getJSONFiles(`${this.path}/contracts/`).forEach((jsonFile) => {
|
||||||
|
let parsed = JSON.parse(readFileSync(jsonFile, "utf-8"));
|
||||||
|
if (allContractClasses[parsed.type] === undefined) return;
|
||||||
|
let chainContract = allContractClasses[parsed.type].fromJson(parsed);
|
||||||
|
if (Contracts[chainContract.getId()])
|
||||||
|
throw new Error(
|
||||||
|
`Multiple contracts with id ${chainContract.getId()} found`
|
||||||
|
);
|
||||||
|
Contracts[chainContract.getId()] = chainContract;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DefaultStore = new Store(`${__dirname}/../store`);
|
|
@ -0,0 +1,381 @@
|
||||||
|
import {
|
||||||
|
RawSigner,
|
||||||
|
SUI_CLOCK_OBJECT_ID,
|
||||||
|
TransactionBlock,
|
||||||
|
JsonRpcProvider,
|
||||||
|
Ed25519Keypair,
|
||||||
|
Connection,
|
||||||
|
ObjectId,
|
||||||
|
} from "@mysten/sui.js";
|
||||||
|
import { readFileSync, writeFileSync } from "fs";
|
||||||
|
import { Chains, SuiChain } from "./chains";
|
||||||
|
import {
|
||||||
|
CHAINS,
|
||||||
|
DataSource,
|
||||||
|
HexString32Bytes,
|
||||||
|
SetFeeInstruction,
|
||||||
|
SuiAuthorizeUpgradeContractInstruction,
|
||||||
|
} from "@pythnetwork/xc-governance-sdk";
|
||||||
|
import { BufferBuilder } from "@pythnetwork/xc-governance-sdk/lib/serialize";
|
||||||
|
import { Contract } from "./base";
|
||||||
|
|
||||||
|
export class SuiContract extends Contract {
|
||||||
|
static type = "SuiContract";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given the ids of the pyth state and wormhole state, create a new SuiContract
|
||||||
|
* The package ids are derived based on the state ids
|
||||||
|
*
|
||||||
|
* @param chain the chain which this contract is deployed on
|
||||||
|
* @param stateId id of the pyth state for the deployed contract
|
||||||
|
* @param wormholeStateId id of the wormhole state for the wormhole contract that pyth binds to
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
public chain: SuiChain,
|
||||||
|
public stateId: string,
|
||||||
|
public wormholeStateId: string
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(parsed: any): SuiContract {
|
||||||
|
if (parsed.type !== SuiContract.type) throw new Error("Invalid type");
|
||||||
|
if (!Chains[parsed.chain])
|
||||||
|
throw new Error(`Chain ${parsed.chain} not found`);
|
||||||
|
return new SuiContract(
|
||||||
|
Chains[parsed.chain] as SuiChain,
|
||||||
|
parsed.stateId,
|
||||||
|
parsed.wormholeStateId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getType(): string {
|
||||||
|
return SuiContract.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
chain: this.chain.id,
|
||||||
|
stateId: this.stateId,
|
||||||
|
wormholeStateId: this.wormholeStateId,
|
||||||
|
type: SuiContract.type,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a objectId, returns the id for the package that the object belongs to.
|
||||||
|
* @param objectId
|
||||||
|
*/
|
||||||
|
async getPackageId(objectId: ObjectId): Promise<ObjectId> {
|
||||||
|
const provider = this.getProvider();
|
||||||
|
const state = await provider
|
||||||
|
.getObject({
|
||||||
|
id: objectId,
|
||||||
|
options: {
|
||||||
|
showContent: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((result) => {
|
||||||
|
if (result.data?.content?.dataType == "moveObject") {
|
||||||
|
return result.data.content.fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error("not move object");
|
||||||
|
});
|
||||||
|
|
||||||
|
if ("upgrade_cap" in state) {
|
||||||
|
return state.upgrade_cap.fields.package;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error("upgrade_cap not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
async getPythPackageId(): Promise<ObjectId> {
|
||||||
|
return await this.getPackageId(this.stateId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getWormholePackageId(): Promise<ObjectId> {
|
||||||
|
return await this.getPackageId(this.wormholeStateId);
|
||||||
|
}
|
||||||
|
|
||||||
|
getId(): string {
|
||||||
|
return `${this.chain.getId()}_${this.stateId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches the price table object id for the current state id
|
||||||
|
*/
|
||||||
|
async getPriceTableId(): Promise<ObjectId> {
|
||||||
|
const provider = this.getProvider();
|
||||||
|
let result = await provider.getDynamicFieldObject({
|
||||||
|
parentId: this.stateId,
|
||||||
|
name: {
|
||||||
|
type: "vector<u8>",
|
||||||
|
value: "price_info",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!result.data) {
|
||||||
|
throw new Error("Price Table not found, contract may not be initialized");
|
||||||
|
}
|
||||||
|
return result.data.objectId;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getPriceFeed(feedId: string) {
|
||||||
|
const tableId = await this.getPriceTableId();
|
||||||
|
const provider = this.getProvider();
|
||||||
|
let result = await provider.getDynamicFieldObject({
|
||||||
|
parentId: tableId,
|
||||||
|
name: {
|
||||||
|
type: `${await this.getPythPackageId()}::price_identifier::PriceIdentifier`,
|
||||||
|
value: {
|
||||||
|
bytes: Array.from(Buffer.from(feedId, "hex")),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!result.data || !result.data.content) {
|
||||||
|
throw new Error("Price feed not found");
|
||||||
|
}
|
||||||
|
if (result.data.content.dataType !== "moveObject") {
|
||||||
|
throw new Error("Price feed type mismatch");
|
||||||
|
}
|
||||||
|
let priceInfoObjectId = result.data.content.fields.value;
|
||||||
|
let priceInfo = await provider.getObject({
|
||||||
|
id: priceInfoObjectId,
|
||||||
|
options: { showContent: true },
|
||||||
|
});
|
||||||
|
if (!priceInfo.data || !priceInfo.data.content) {
|
||||||
|
throw new Error(
|
||||||
|
`Price feed ID ${priceInfoObjectId} in price table but object not found!!`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (priceInfo.data.content.dataType !== "moveObject") {
|
||||||
|
throw new Error(
|
||||||
|
`Expected ${priceInfoObjectId} to be a moveObject (PriceInfoObject)`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return priceInfo.data.content.fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a signed VAA, execute the migration instruction on the pyth contract.
|
||||||
|
* The payload of the VAA can be obtained from the `getUpgradePackagePayload` method.
|
||||||
|
* @param vaa
|
||||||
|
* @param keypair used to sign the transaction
|
||||||
|
*/
|
||||||
|
async executeMigrateInstruction(vaa: Buffer, keypair: Ed25519Keypair) {
|
||||||
|
const tx = new TransactionBlock();
|
||||||
|
const packageId = await this.getPythPackageId();
|
||||||
|
let decreeReceipt = await this.getVaaDecreeReceipt(tx, packageId, vaa);
|
||||||
|
|
||||||
|
tx.moveCall({
|
||||||
|
target: `${packageId}::migrate::migrate`,
|
||||||
|
arguments: [tx.object(this.stateId), decreeReceipt],
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.executeTransaction(tx, keypair);
|
||||||
|
}
|
||||||
|
|
||||||
|
getUpgradePackagePayload(digest: string): Buffer {
|
||||||
|
let setFee = new SuiAuthorizeUpgradeContractInstruction(
|
||||||
|
CHAINS["sui"],
|
||||||
|
new HexString32Bytes(digest)
|
||||||
|
).serialize();
|
||||||
|
return this.wrapWithWormholeGovernancePayload(0, setFee);
|
||||||
|
}
|
||||||
|
|
||||||
|
getSetUpdateFeePayload(fee: number): Buffer {
|
||||||
|
let setFee = new SetFeeInstruction(
|
||||||
|
CHAINS["sui"],
|
||||||
|
BigInt(fee),
|
||||||
|
BigInt(0)
|
||||||
|
).serialize();
|
||||||
|
return this.wrapWithWormholeGovernancePayload(3, setFee);
|
||||||
|
}
|
||||||
|
|
||||||
|
async executeGovernanceInstruction(vaa: Buffer, keypair: Ed25519Keypair) {
|
||||||
|
const tx = new TransactionBlock();
|
||||||
|
const packageId = await this.getPythPackageId();
|
||||||
|
let decreeReceipt = await this.getVaaDecreeReceipt(tx, packageId, vaa);
|
||||||
|
|
||||||
|
tx.moveCall({
|
||||||
|
target: `${packageId}::governance::execute_governance_instruction`,
|
||||||
|
arguments: [tx.object(this.stateId), decreeReceipt],
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.executeTransaction(tx, keypair);
|
||||||
|
}
|
||||||
|
|
||||||
|
async executeUpgradeInstruction(
|
||||||
|
vaa: Buffer,
|
||||||
|
keypair: Ed25519Keypair,
|
||||||
|
modules: number[][],
|
||||||
|
dependencies: string[]
|
||||||
|
) {
|
||||||
|
const tx = new TransactionBlock();
|
||||||
|
const packageId = await this.getPythPackageId();
|
||||||
|
let decreeReceipt = await this.getVaaDecreeReceipt(tx, packageId, vaa);
|
||||||
|
|
||||||
|
const [upgradeTicket] = tx.moveCall({
|
||||||
|
target: `${packageId}::contract_upgrade::authorize_upgrade`,
|
||||||
|
arguments: [tx.object(this.stateId), decreeReceipt],
|
||||||
|
});
|
||||||
|
|
||||||
|
const [upgradeReceipt] = tx.upgrade({
|
||||||
|
modules,
|
||||||
|
dependencies,
|
||||||
|
packageId: packageId,
|
||||||
|
ticket: upgradeTicket,
|
||||||
|
});
|
||||||
|
|
||||||
|
tx.moveCall({
|
||||||
|
target: `${packageId}::contract_upgrade::commit_upgrade`,
|
||||||
|
arguments: [tx.object(this.stateId), upgradeReceipt],
|
||||||
|
});
|
||||||
|
return this.executeTransaction(tx, keypair);
|
||||||
|
}
|
||||||
|
|
||||||
|
private wrapWithWormholeGovernancePayload(
|
||||||
|
actionVariant: number,
|
||||||
|
payload: Buffer
|
||||||
|
): Buffer {
|
||||||
|
const builder = new BufferBuilder();
|
||||||
|
builder.addBuffer(
|
||||||
|
Buffer.from(
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
|
"hex"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
builder.addUint8(actionVariant);
|
||||||
|
builder.addUint16(CHAINS["sui"]); // should always be sui (21) no matter devnet or testnet
|
||||||
|
builder.addBuffer(payload);
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility function to get the decree receipt object for a VAA that can be
|
||||||
|
* used to authorize a governance instruction.
|
||||||
|
* @param tx
|
||||||
|
* @param packageId pyth package id
|
||||||
|
* @param vaa
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private async getVaaDecreeReceipt(
|
||||||
|
tx: TransactionBlock,
|
||||||
|
packageId: string,
|
||||||
|
vaa: Buffer
|
||||||
|
) {
|
||||||
|
const wormholePackageId = await this.getWormholePackageId();
|
||||||
|
let [decreeTicket] = tx.moveCall({
|
||||||
|
target: `${packageId}::set_update_fee::authorize_governance`,
|
||||||
|
arguments: [tx.object(this.stateId), tx.pure(false)],
|
||||||
|
});
|
||||||
|
|
||||||
|
let [verifiedVAA] = tx.moveCall({
|
||||||
|
target: `${wormholePackageId}::vaa::parse_and_verify`,
|
||||||
|
arguments: [
|
||||||
|
tx.object(this.wormholeStateId),
|
||||||
|
tx.pure(Array.from(vaa)),
|
||||||
|
tx.object(SUI_CLOCK_OBJECT_ID),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
let [decreeReceipt] = tx.moveCall({
|
||||||
|
target: `${wormholePackageId}::governance_message::verify_vaa`,
|
||||||
|
arguments: [tx.object(this.wormholeStateId), verifiedVAA, decreeTicket],
|
||||||
|
typeArguments: [`${packageId}::governance_witness::GovernanceWitness`],
|
||||||
|
});
|
||||||
|
return decreeReceipt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a transaction block and a keypair, sign and execute it
|
||||||
|
* Sets the gas budget to 2x the estimated gas cost
|
||||||
|
* @param tx
|
||||||
|
* @param keypair
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private async executeTransaction(
|
||||||
|
tx: TransactionBlock,
|
||||||
|
keypair: Ed25519Keypair
|
||||||
|
) {
|
||||||
|
const provider = this.getProvider();
|
||||||
|
let txBlock = {
|
||||||
|
transactionBlock: tx,
|
||||||
|
options: {
|
||||||
|
showEffects: true,
|
||||||
|
showEvents: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const wallet = new RawSigner(keypair, provider);
|
||||||
|
let gasCost = await wallet.getGasCostEstimation(txBlock);
|
||||||
|
tx.setGasBudget(gasCost * BigInt(2));
|
||||||
|
return wallet.signAndExecuteTransactionBlock(txBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getValidTimePeriod() {
|
||||||
|
const fields = await this.getStateFields();
|
||||||
|
return Number(fields.stale_price_threshold);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getDataSources(): Promise<DataSource[]> {
|
||||||
|
const provider = this.getProvider();
|
||||||
|
let result = await provider.getDynamicFieldObject({
|
||||||
|
parentId: this.stateId,
|
||||||
|
name: {
|
||||||
|
type: "vector<u8>",
|
||||||
|
value: "data_sources",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!result.data || !result.data.content) {
|
||||||
|
throw new Error(
|
||||||
|
"Data Sources not found, contract may not be initialized"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (result.data.content.dataType !== "moveObject") {
|
||||||
|
throw new Error("Data Sources type mismatch");
|
||||||
|
}
|
||||||
|
return result.data.content.fields.value.fields.keys.map(
|
||||||
|
({ fields }: any) => {
|
||||||
|
return new DataSource(
|
||||||
|
Number(fields.emitter_chain),
|
||||||
|
new HexString32Bytes(
|
||||||
|
Buffer.from(
|
||||||
|
fields.emitter_address.fields.value.fields.data
|
||||||
|
).toString("hex")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getGovernanceDataSource(): Promise<DataSource> {
|
||||||
|
const fields = await this.getStateFields();
|
||||||
|
const governanceFields = fields.governance_data_source.fields;
|
||||||
|
const chainId = governanceFields.emitter_chain;
|
||||||
|
const emitterAddress =
|
||||||
|
governanceFields.emitter_address.fields.value.fields.data;
|
||||||
|
return new DataSource(
|
||||||
|
Number(chainId),
|
||||||
|
new HexString32Bytes(Buffer.from(emitterAddress).toString("hex"))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getProvider() {
|
||||||
|
return new JsonRpcProvider(new Connection({ fullnode: this.chain.rpcUrl }));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getStateFields() {
|
||||||
|
const provider = this.getProvider();
|
||||||
|
const result = await provider.getObject({
|
||||||
|
id: this.stateId,
|
||||||
|
options: { showContent: true },
|
||||||
|
});
|
||||||
|
if (
|
||||||
|
!result.data ||
|
||||||
|
!result.data.content ||
|
||||||
|
result.data.content.dataType !== "moveObject"
|
||||||
|
)
|
||||||
|
throw new Error("Unable to fetch pyth state object");
|
||||||
|
return result.data.content.fields;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
import {
|
||||||
|
Vault,
|
||||||
|
Contracts,
|
||||||
|
Vaults,
|
||||||
|
loadHotWallet,
|
||||||
|
WormholeEmitter,
|
||||||
|
} from "./entities";
|
||||||
|
import { SuiContract } from "./sui";
|
||||||
|
import { CosmWasmContract } from "./cosmwasm";
|
||||||
|
import { Ed25519Keypair, RawSigner } from "@mysten/sui.js";
|
||||||
|
import { DefaultStore } from "./store";
|
||||||
|
import { Chains } from "./chains";
|
||||||
|
|
||||||
|
async function test() {
|
||||||
|
// Deploy the same cosmwasm code with different config
|
||||||
|
|
||||||
|
// let c = Contracts.osmosis_testnet_5_osmo1lltupx02sj99suakmuk4sr4ppqf34ajedaxut3ukjwkv6469erwqtpg9t3 as CosmWasmContract;
|
||||||
|
// let old_conf = await c.getConfig();
|
||||||
|
// let config = CosmWasmContract.getDeploymentConfig(c.chain, 'edge', old_conf.config_v1.wormhole_contract);
|
||||||
|
// console.log(config);
|
||||||
|
// config.governance_source.emitter = wallet.publicKey.toBuffer().toString('base64');
|
||||||
|
// let mnemonic = 'FILLME'
|
||||||
|
// console.log(await CosmWasmContract.deploy(c.chain, await c.getCodeId(), config, mnemonic));
|
||||||
|
|
||||||
|
let s = DefaultStore;
|
||||||
|
Object.values(Contracts).forEach((c) => {
|
||||||
|
console.log(c);
|
||||||
|
s.save(c);
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.values(Chains).forEach((c) => {
|
||||||
|
console.log(c);
|
||||||
|
s.save(c);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Execute some governance instruction on sui contract
|
||||||
|
|
||||||
|
// let c = Contracts.sui_testnet_0x651dcb84d579fcdf51f15d79eb28f7e10b416c9202b6a156495bb1a4aecd55ea as SuiContract
|
||||||
|
// let wallet = await loadHotWallet('/tmp/priv.json');
|
||||||
|
// let emitter = new WormholeEmitter("devnet", wallet);
|
||||||
|
// let proposal = c.setUpdateFee(200);
|
||||||
|
// let submittedWormholeMessage = await emitter.sendMessage(proposal);
|
||||||
|
// let vaa = await submittedWormholeMessage.fetchVAA(10);
|
||||||
|
// const keypair = Ed25519Keypair.fromSecretKey(Buffer.from('FILLME', "hex"));
|
||||||
|
// await c.executeGovernanceInstruction(vaa);
|
||||||
|
}
|
||||||
|
|
||||||
|
test();
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"querierEndpoint": "https://rpc.uni.junonetwork.io/",
|
||||||
|
"executorEndpoint": "https://rpc.uni.junonetwork.io/",
|
||||||
|
"id": "juno_testnet",
|
||||||
|
"gasPrice": "0.025",
|
||||||
|
"prefix": "juno",
|
||||||
|
"feeDenom": "ujunox",
|
||||||
|
"type": "CosmWasmChain"
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"querierEndpoint": "https://rpc-kralum.neutron-1.neutron.org",
|
||||||
|
"executorEndpoint": "https://rpc-kralum.neutron-1.neutron.org",
|
||||||
|
"id": "neutron",
|
||||||
|
"gasPrice": "0.025",
|
||||||
|
"prefix": "neutron",
|
||||||
|
"feeDenom": "untrn",
|
||||||
|
"type": "CosmWasmChain"
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"querierEndpoint": "https://rpc.pion.rs-testnet.polypore.xyz/",
|
||||||
|
"executorEndpoint": "https://rpc.pion.rs-testnet.polypore.xyz/",
|
||||||
|
"id": "neutron_testnet_pion_1",
|
||||||
|
"gasPrice": "0.05",
|
||||||
|
"prefix": "neutron",
|
||||||
|
"feeDenom": "untrn",
|
||||||
|
"type": "CosmWasmChain"
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"querierEndpoint": "https://rpc.osmotest5.osmosis.zone/",
|
||||||
|
"executorEndpoint": "https://rpc.osmotest5.osmosis.zone/",
|
||||||
|
"id": "osmosis_testnet_5",
|
||||||
|
"gasPrice": "0.025",
|
||||||
|
"prefix": "osmo",
|
||||||
|
"feeDenom": "uosmo",
|
||||||
|
"type": "CosmWasmChain"
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"querierEndpoint": "https://sei-rpc.polkachu.com",
|
||||||
|
"executorEndpoint": "https://sei-rpc.polkachu.com",
|
||||||
|
"id": "sei_pacific_1",
|
||||||
|
"gasPrice": "0.025",
|
||||||
|
"prefix": "sei",
|
||||||
|
"feeDenom": "usei",
|
||||||
|
"type": "CosmWasmChain"
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"querierEndpoint": "https://rpc.atlantic-2.seinetwork.io/",
|
||||||
|
"executorEndpoint": "https://rpc.atlantic-2.seinetwork.io/",
|
||||||
|
"id": "sei_testnet_atlantic_2",
|
||||||
|
"gasPrice": "0.01",
|
||||||
|
"prefix": "sei",
|
||||||
|
"feeDenom": "usei",
|
||||||
|
"type": "CosmWasmChain"
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"id": "arbitrum_testnet",
|
||||||
|
"rpcUrl": "https://goerli-rollup.arbitrum.io/rpc",
|
||||||
|
"type": "EVMChain"
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"id": "cronos",
|
||||||
|
"rpcUrl": "https://cronosrpc-1.xstaking.sg",
|
||||||
|
"type": "EVMChain"
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"id": "cronos_testnet",
|
||||||
|
"rpcUrl": "https://evm-t3.cronos.org",
|
||||||
|
"type": "EVMChain"
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"id": "sui_devnet",
|
||||||
|
"rpcUrl": "https://fullnode.devnet.sui.io:443",
|
||||||
|
"type": "SuiChain"
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"id": "sui_mainnet",
|
||||||
|
"rpcUrl": "https://fullnode.mainnet.sui.io:443",
|
||||||
|
"type": "SuiChain"
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"id": "sui_testnet",
|
||||||
|
"rpcUrl": "https://fullnode.testnet.sui.io:443",
|
||||||
|
"type": "SuiChain"
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"chain": "juno_testnet",
|
||||||
|
"address": "juno1h93q9kwlnfml2gum4zj54al9w4jdmuhtzrh6vhycnemsqlqv9l9snnznxs",
|
||||||
|
"type": "CosmWasmContract"
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"chain": "neutron",
|
||||||
|
"address": "neutron1m2emc93m9gpwgsrsf2vylv9xvgqh654630v7dfrhrkmr5slly53spg85wv",
|
||||||
|
"type": "CosmWasmContract"
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"chain": "neutron_testnet_pion_1",
|
||||||
|
"address": "neutron1xxmcu6wxgawjlajx8jalyk9cxsudnygjg0tvjesfyurh4utvtpes5wmpjp",
|
||||||
|
"type": "CosmWasmContract"
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"chain": "osmosis_testnet_5",
|
||||||
|
"address": "osmo1lltupx02sj99suakmuk4sr4ppqf34ajedaxut3ukjwkv6469erwqtpg9t3",
|
||||||
|
"type": "CosmWasmContract"
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"chain": "sei_testnet_atlantic_2",
|
||||||
|
"address": "sei1w2rxq6eckak47s25crxlhmq96fzjwdtjgdwavn56ggc0qvxvw7rqczxyfy",
|
||||||
|
"type": "CosmWasmContract"
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"chain": "cronos",
|
||||||
|
"address": "0xe0d0e68297772dd5a1f1d99897c581e2082dba5b",
|
||||||
|
"type": "EVMContract"
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"chain": "cronos_testnet",
|
||||||
|
"address": "0xFF125F377F9F7631a05f4B01CeD32a6A2ab843C7",
|
||||||
|
"type": "EVMContract"
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"chain": "sui_mainnet",
|
||||||
|
"stateId": "0xf9ff3ef935ef6cdfb659a203bf2754cebeb63346e29114a535ea6f41315e5a3f",
|
||||||
|
"wormholeStateId": "0xaeab97f96cf9877fee2883315d459552b2b921edc16d7ceac6eab944dd88919c",
|
||||||
|
"type": "SuiContract"
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"chain": "sui_testnet",
|
||||||
|
"stateId": "0xb3142a723792001caafc601b7c6fe38f09f3684e360b56d8d90fc574e71e75f3",
|
||||||
|
"wormholeStateId": "0xebba4cc4d614f7a7cdbe883acc76d1cc767922bc96778e7b68be0d15fce27c02",
|
||||||
|
"type": "SuiContract"
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"chain": "sui_testnet",
|
||||||
|
"stateId": "0xe8c2ddcd5b10e8ed98e53b12fcf8f0f6fd9315f810ae61fa4001858851f21c88",
|
||||||
|
"wormholeStateId": "0xebba4cc4d614f7a7cdbe883acc76d1cc767922bc96778e7b68be0d15fce27c02",
|
||||||
|
"type": "SuiContract"
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"extends": "../tsconfig.base.json",
|
||||||
|
"include": ["src"],
|
||||||
|
"exclude": ["node_modules", "**/__tests__/*"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"rootDir": "src/",
|
||||||
|
"outDir": "./lib"
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
export {
|
export {
|
||||||
DataSource,
|
DataSource,
|
||||||
AptosAuthorizeUpgradeContractInstruction,
|
AptosAuthorizeUpgradeContractInstruction,
|
||||||
|
SuiAuthorizeUpgradeContractInstruction,
|
||||||
EthereumUpgradeContractInstruction,
|
EthereumUpgradeContractInstruction,
|
||||||
EthereumSetWormholeAddress,
|
EthereumSetWormholeAddress,
|
||||||
HexString20Bytes,
|
HexString20Bytes,
|
||||||
|
|
|
@ -111,6 +111,16 @@ export class AptosAuthorizeUpgradeContractInstruction extends TargetInstruction
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class SuiAuthorizeUpgradeContractInstruction extends TargetInstruction {
|
||||||
|
constructor(targetChainId: ChainId, private digest: HexString32Bytes) {
|
||||||
|
super(TargetAction.UpgradeContract, targetChainId);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected serializePayload(): Buffer {
|
||||||
|
return this.digest.serialize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class EthereumUpgradeContractInstruction extends TargetInstruction {
|
export class EthereumUpgradeContractInstruction extends TargetInstruction {
|
||||||
constructor(targetChainId: ChainId, private address: HexString20Bytes) {
|
constructor(targetChainId: ChainId, private address: HexString20Bytes) {
|
||||||
super(TargetAction.UpgradeContract, targetChainId);
|
super(TargetAction.UpgradeContract, targetChainId);
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"name": "@pythnetwork/cosmwasm-deploy-tools",
|
"name": "@pythnetwork/cosmwasm-deploy-tools",
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "deploy-pyth-bridge.ts",
|
"main": "./src/index.ts",
|
||||||
"private": "true",
|
"private": "true",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from "./cosmwasm";
|
||||||
|
export * from "./injective";
|
|
@ -0,0 +1,3 @@
|
||||||
|
export * from "./deployer";
|
||||||
|
export * from "./chains-manager";
|
||||||
|
export * from "./pyth-wrapper";
|
|
@ -1,16 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"contractName": "Migrations",
|
|
||||||
"address": "0x1c6Cd107fB71768FBc46F8B6180Eec155C03eEb5"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"contractName": "WormholeReceiver",
|
|
||||||
"address": "0x15D35b8985e350f783fe3d95401401E194ff1E6f",
|
|
||||||
"transactionHash": "0x1c33d9b6971f7337e0e2ea390affe18fe90709dcb803712f6d8bb4a008705fb7"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"contractName": "PythUpgradable",
|
|
||||||
"address": "0xBAEA4A1A2Eaa4E9bb78f2303C213Da152933170E",
|
|
||||||
"transactionHash": "0x507d747b3c978794cc880a201c009d37367f66925b930c0cebc30c493c9d31eb"
|
|
||||||
}
|
|
||||||
]
|
|
Loading…
Reference in New Issue