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:
Mohammad Amin Khashkhashi Moghaddam 2023-07-12 15:50:24 +02:00 committed by GitHub
parent a77ee78d13
commit 66e5f186b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 1645 additions and 17 deletions

View File

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

View File

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

View File

@ -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> = {};

View File

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

View File

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

134
contract_manager/src/evm.ts Normal file
View File

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

View File

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

View File

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

381
contract_manager/src/sui.ts Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,5 @@
{
"id": "cronos",
"rpcUrl": "https://cronosrpc-1.xstaking.sg",
"type": "EVMChain"
}

View File

@ -0,0 +1,5 @@
{
"id": "cronos_testnet",
"rpcUrl": "https://evm-t3.cronos.org",
"type": "EVMChain"
}

View File

@ -0,0 +1,5 @@
{
"id": "sui_devnet",
"rpcUrl": "https://fullnode.devnet.sui.io:443",
"type": "SuiChain"
}

View File

@ -0,0 +1,5 @@
{
"id": "sui_mainnet",
"rpcUrl": "https://fullnode.mainnet.sui.io:443",
"type": "SuiChain"
}

View File

@ -0,0 +1,5 @@
{
"id": "sui_testnet",
"rpcUrl": "https://fullnode.testnet.sui.io:443",
"type": "SuiChain"
}

View File

@ -0,0 +1,5 @@
{
"chain": "juno_testnet",
"address": "juno1h93q9kwlnfml2gum4zj54al9w4jdmuhtzrh6vhycnemsqlqv9l9snnznxs",
"type": "CosmWasmContract"
}

View File

@ -0,0 +1,5 @@
{
"chain": "neutron",
"address": "neutron1m2emc93m9gpwgsrsf2vylv9xvgqh654630v7dfrhrkmr5slly53spg85wv",
"type": "CosmWasmContract"
}

View File

@ -0,0 +1,5 @@
{
"chain": "neutron_testnet_pion_1",
"address": "neutron1xxmcu6wxgawjlajx8jalyk9cxsudnygjg0tvjesfyurh4utvtpes5wmpjp",
"type": "CosmWasmContract"
}

View File

@ -0,0 +1,5 @@
{
"chain": "osmosis_testnet_5",
"address": "osmo1lltupx02sj99suakmuk4sr4ppqf34ajedaxut3ukjwkv6469erwqtpg9t3",
"type": "CosmWasmContract"
}

View File

@ -0,0 +1,5 @@
{
"chain": "sei_testnet_atlantic_2",
"address": "sei1w2rxq6eckak47s25crxlhmq96fzjwdtjgdwavn56ggc0qvxvw7rqczxyfy",
"type": "CosmWasmContract"
}

View File

@ -0,0 +1,5 @@
{
"chain": "cronos",
"address": "0xe0d0e68297772dd5a1f1d99897c581e2082dba5b",
"type": "EVMContract"
}

View File

@ -0,0 +1,5 @@
{
"chain": "cronos_testnet",
"address": "0xFF125F377F9F7631a05f4B01CeD32a6A2ab843C7",
"type": "EVMContract"
}

View File

@ -0,0 +1,6 @@
{
"chain": "sui_mainnet",
"stateId": "0xf9ff3ef935ef6cdfb659a203bf2754cebeb63346e29114a535ea6f41315e5a3f",
"wormholeStateId": "0xaeab97f96cf9877fee2883315d459552b2b921edc16d7ceac6eab944dd88919c",
"type": "SuiContract"
}

View File

@ -0,0 +1,6 @@
{
"chain": "sui_testnet",
"stateId": "0xb3142a723792001caafc601b7c6fe38f09f3684e360b56d8d90fc574e71e75f3",
"wormholeStateId": "0xebba4cc4d614f7a7cdbe883acc76d1cc767922bc96778e7b68be0d15fce27c02",
"type": "SuiContract"
}

View File

@ -0,0 +1,6 @@
{
"chain": "sui_testnet",
"stateId": "0xe8c2ddcd5b10e8ed98e53b12fcf8f0f6fd9315f810ae61fa4001858851f21c88",
"wormholeStateId": "0xebba4cc4d614f7a7cdbe883acc76d1cc767922bc96778e7b68be0d15fce27c02",
"type": "SuiContract"
}

View File

@ -0,0 +1,9 @@
{
"extends": "../tsconfig.base.json",
"include": ["src"],
"exclude": ["node_modules", "**/__tests__/*"],
"compilerOptions": {
"rootDir": "src/",
"outDir": "./lib"
}
}

View File

@ -1,6 +1,7 @@
export { export {
DataSource, DataSource,
AptosAuthorizeUpgradeContractInstruction, AptosAuthorizeUpgradeContractInstruction,
SuiAuthorizeUpgradeContractInstruction,
EthereumUpgradeContractInstruction, EthereumUpgradeContractInstruction,
EthereumSetWormholeAddress, EthereumSetWormholeAddress,
HexString20Bytes, HexString20Bytes,

View File

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

View File

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

View File

@ -0,0 +1,2 @@
export * from "./cosmwasm";
export * from "./injective";

View File

@ -0,0 +1,3 @@
export * from "./deployer";
export * from "./chains-manager";
export * from "./pyth-wrapper";

View File

@ -1,16 +0,0 @@
[
{
"contractName": "Migrations",
"address": "0x1c6Cd107fB71768FBc46F8B6180Eec155C03eEb5"
},
{
"contractName": "WormholeReceiver",
"address": "0x15D35b8985e350f783fe3d95401401E194ff1E6f",
"transactionHash": "0x1c33d9b6971f7337e0e2ea390affe18fe90709dcb803712f6d8bb4a008705fb7"
},
{
"contractName": "PythUpgradable",
"address": "0xBAEA4A1A2Eaa4E9bb78f2303C213Da152933170E",
"transactionHash": "0x507d747b3c978794cc880a201c009d37367f66925b930c0cebc30c493c9d31eb"
}
]