[contract-manager] General improvements (#1024)
* Better return types for transaction execution functions * Use new type for passing PrivateKey * Small typing improvements * Stricter liniting * Rename edge to beta for DeploymentType * Reduce any usage across the contract manager * Fix explicit any linting warnings
This commit is contained in:
parent
f316a512c3
commit
b9dbd54d5a
|
@ -0,0 +1,6 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
parser: "@typescript-eslint/parser",
|
||||
plugins: ["@typescript-eslint"],
|
||||
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
|
||||
};
|
|
@ -10,7 +10,8 @@
|
|||
],
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"shell": "ts-node ./src/shell.ts"
|
||||
"shell": "ts-node ./src/shell.ts",
|
||||
"lint": "eslint src/"
|
||||
},
|
||||
"author": "",
|
||||
"license": "Apache-2.0",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import yargs from "yargs";
|
||||
import { hideBin } from "yargs/helpers";
|
||||
import { DefaultStore } from "../src";
|
||||
import { DefaultStore, toPrivateKey } from "../src";
|
||||
import { PriceServiceConnection } from "@pythnetwork/price-service-client";
|
||||
|
||||
const parser = yargs(hideBin(process.argv))
|
||||
|
@ -43,9 +43,12 @@ async function main() {
|
|||
argv.endpoint || defaultEndpoint
|
||||
);
|
||||
const vaas = await priceService.getLatestVaas(argv["feed-id"] as string[]);
|
||||
await contract.executeUpdatePriceFeed(
|
||||
argv["private-key"],
|
||||
vaas.map((vaa) => Buffer.from(vaa, "base64"))
|
||||
const privateKey = toPrivateKey(argv["private-key"]);
|
||||
console.log(
|
||||
await contract.executeUpdatePriceFeed(
|
||||
privateKey,
|
||||
vaas.map((vaa) => Buffer.from(vaa, "base64"))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,24 @@
|
|||
import { DataSource } from "xc_admin_common";
|
||||
import { Chain } from "./chains";
|
||||
|
||||
export interface TxResult {
|
||||
id: string;
|
||||
info: unknown; // chain specific info
|
||||
}
|
||||
|
||||
export type DeploymentType = "stable" | "beta";
|
||||
export type PrivateKey = string & { __type: "PrivateKey" };
|
||||
function checkIsPrivateKey(key: string): asserts key is PrivateKey {
|
||||
if (Buffer.from(key, "hex").length !== 32)
|
||||
throw new Error("Invalid private key, must be 64 hex chars");
|
||||
}
|
||||
export function toPrivateKey(key: string): PrivateKey {
|
||||
checkIsPrivateKey(key);
|
||||
return key;
|
||||
}
|
||||
|
||||
export type KeyValueConfig = Record<string, string | number | boolean>;
|
||||
|
||||
export abstract class Storable {
|
||||
/**
|
||||
* Returns the unique identifier for this object
|
||||
|
@ -17,7 +35,7 @@ export abstract class Storable {
|
|||
* 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;
|
||||
abstract toJson(): KeyValueConfig;
|
||||
}
|
||||
|
||||
export interface Price {
|
||||
|
@ -75,9 +93,9 @@ export abstract class Contract extends Storable {
|
|||
* @param vaas an array of VAAs containing price update messages to execute
|
||||
*/
|
||||
abstract executeUpdatePriceFeed(
|
||||
senderPrivateKey: string,
|
||||
senderPrivateKey: PrivateKey,
|
||||
vaas: Buffer[]
|
||||
): Promise<any>;
|
||||
): Promise<TxResult>;
|
||||
|
||||
/**
|
||||
* Executes the governance instruction contained in the VAA using the sender credentials
|
||||
|
@ -85,9 +103,9 @@ export abstract class Contract extends Storable {
|
|||
* @param vaa the VAA to execute
|
||||
*/
|
||||
abstract executeGovernanceInstruction(
|
||||
senderPrivateKey: string,
|
||||
senderPrivateKey: PrivateKey,
|
||||
vaa: Buffer
|
||||
): Promise<any>;
|
||||
): Promise<TxResult>;
|
||||
|
||||
/**
|
||||
* Returns the single data source that this contract accepts governance messages from
|
||||
|
@ -95,11 +113,11 @@ export abstract class Contract extends Storable {
|
|||
abstract getGovernanceDataSource(): Promise<DataSource>;
|
||||
}
|
||||
|
||||
export function getDefaultDeploymentConfig(deploymentType: "stable" | "edge"): {
|
||||
export function getDefaultDeploymentConfig(deploymentType: DeploymentType): {
|
||||
dataSources: DataSource[];
|
||||
governanceDataSource: DataSource;
|
||||
wormholeConfig: {
|
||||
governanceChainId: Number;
|
||||
governanceChainId: number;
|
||||
governanceContract: string; // 32 byte address in 64 char hex format
|
||||
initialGuardianSet: string[]; // 20 byte addresses in 40 char hex format
|
||||
};
|
||||
|
@ -135,7 +153,7 @@ export function getDefaultDeploymentConfig(deploymentType: "stable" | "edge"): {
|
|||
initialGuardianSet: ["58cc3ae5c097b213ce3c81979e1b9f9570746aa5"],
|
||||
},
|
||||
};
|
||||
else if (deploymentType === "edge")
|
||||
else if (deploymentType === "beta")
|
||||
return {
|
||||
dataSources: [
|
||||
{
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { Storable } from "./base";
|
||||
import { KeyValueConfig, PrivateKey, Storable } from "./base";
|
||||
import {
|
||||
ChainName,
|
||||
CHAINS,
|
||||
SetFee,
|
||||
CosmosUpgradeContract,
|
||||
EvmUpgradeContract,
|
||||
|
@ -22,6 +21,10 @@ import {
|
|||
} from "@pythnetwork/cosmwasm-deploy-tools";
|
||||
import { Network } from "@injectivelabs/networks";
|
||||
|
||||
export type ChainConfig = Record<string, string> & {
|
||||
mainnet: boolean;
|
||||
id: string;
|
||||
};
|
||||
export abstract class Chain extends Storable {
|
||||
public wormholeChainName: ChainName;
|
||||
|
||||
|
@ -92,15 +95,15 @@ export abstract class Chain extends Storable {
|
|||
* Returns the payload for a governance contract upgrade instruction for contracts deployed on this chain
|
||||
* @param upgradeInfo based on the contract type, this can be a contract address, codeId, package digest, etc.
|
||||
*/
|
||||
abstract generateGovernanceUpgradePayload(upgradeInfo: any): Buffer;
|
||||
abstract generateGovernanceUpgradePayload(upgradeInfo: unknown): Buffer;
|
||||
}
|
||||
|
||||
export class GlobalChain extends Chain {
|
||||
static type: string = "GlobalChain";
|
||||
static type = "GlobalChain";
|
||||
constructor() {
|
||||
super("global", true, "unset");
|
||||
}
|
||||
generateGovernanceUpgradePayload(upgradeInfo: any): Buffer {
|
||||
generateGovernanceUpgradePayload(): Buffer {
|
||||
throw new Error(
|
||||
"Can not create a governance message for upgrading contracts on all chains!"
|
||||
);
|
||||
|
@ -110,7 +113,7 @@ export class GlobalChain extends Chain {
|
|||
return GlobalChain.type;
|
||||
}
|
||||
|
||||
toJson(): any {
|
||||
toJson(): KeyValueConfig {
|
||||
return {
|
||||
id: this.id,
|
||||
wormholeChainName: this.wormholeChainName,
|
||||
|
@ -121,7 +124,7 @@ export class GlobalChain extends Chain {
|
|||
}
|
||||
|
||||
export class CosmWasmChain extends Chain {
|
||||
static type: string = "CosmWasmChain";
|
||||
static type = "CosmWasmChain";
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
|
@ -135,7 +138,7 @@ export class CosmWasmChain extends Chain {
|
|||
super(id, mainnet, wormholeChainName);
|
||||
}
|
||||
|
||||
static fromJson(parsed: any): CosmWasmChain {
|
||||
static fromJson(parsed: ChainConfig): CosmWasmChain {
|
||||
if (parsed.type !== CosmWasmChain.type) throw new Error("Invalid type");
|
||||
return new CosmWasmChain(
|
||||
parsed.id,
|
||||
|
@ -148,7 +151,7 @@ export class CosmWasmChain extends Chain {
|
|||
);
|
||||
}
|
||||
|
||||
toJson(): any {
|
||||
toJson(): KeyValueConfig {
|
||||
return {
|
||||
endpoint: this.endpoint,
|
||||
id: this.id,
|
||||
|
@ -174,7 +177,7 @@ export class CosmWasmChain extends Chain {
|
|||
return new CosmosUpgradeContract(this.wormholeChainName, codeId).encode();
|
||||
}
|
||||
|
||||
async getExecutor(privateKey: string) {
|
||||
async getExecutor(privateKey: PrivateKey) {
|
||||
if (this.getId().indexOf("injective") > -1) {
|
||||
return InjectiveExecutor.fromPrivateKey(
|
||||
this.isMainnet() ? Network.Mainnet : Network.Testnet,
|
||||
|
@ -190,7 +193,7 @@ export class CosmWasmChain extends Chain {
|
|||
}
|
||||
|
||||
export class SuiChain extends Chain {
|
||||
static type: string = "SuiChain";
|
||||
static type = "SuiChain";
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
|
@ -201,7 +204,7 @@ export class SuiChain extends Chain {
|
|||
super(id, mainnet, wormholeChainName);
|
||||
}
|
||||
|
||||
static fromJson(parsed: any): SuiChain {
|
||||
static fromJson(parsed: ChainConfig): SuiChain {
|
||||
if (parsed.type !== SuiChain.type) throw new Error("Invalid type");
|
||||
return new SuiChain(
|
||||
parsed.id,
|
||||
|
@ -211,7 +214,7 @@ export class SuiChain extends Chain {
|
|||
);
|
||||
}
|
||||
|
||||
toJson(): any {
|
||||
toJson(): KeyValueConfig {
|
||||
return {
|
||||
id: this.id,
|
||||
wormholeChainName: this.wormholeChainName,
|
||||
|
@ -238,7 +241,7 @@ export class SuiChain extends Chain {
|
|||
}
|
||||
|
||||
export class EvmChain extends Chain {
|
||||
static type: string = "EvmChain";
|
||||
static type = "EvmChain";
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
|
@ -250,7 +253,7 @@ export class EvmChain extends Chain {
|
|||
super(id, mainnet, wormholeChainName);
|
||||
}
|
||||
|
||||
static fromJson(parsed: any): EvmChain {
|
||||
static fromJson(parsed: ChainConfig & { networkId: number }): EvmChain {
|
||||
if (parsed.type !== EvmChain.type) throw new Error("Invalid type");
|
||||
return new EvmChain(
|
||||
parsed.id,
|
||||
|
@ -277,7 +280,7 @@ export class EvmChain extends Chain {
|
|||
return new EvmSetWormholeAddress(this.wormholeChainName, address).encode();
|
||||
}
|
||||
|
||||
toJson(): any {
|
||||
toJson(): KeyValueConfig {
|
||||
return {
|
||||
id: this.id,
|
||||
wormholeChainName: this.wormholeChainName,
|
||||
|
@ -311,10 +314,10 @@ export class EvmChain extends Chain {
|
|||
* @returns the address of the deployed contract
|
||||
*/
|
||||
async deploy(
|
||||
privateKey: string,
|
||||
abi: any,
|
||||
privateKey: PrivateKey,
|
||||
abi: any, // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
bytecode: string,
|
||||
deployArgs: any[]
|
||||
deployArgs: any[] // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
): Promise<string> {
|
||||
const web3 = new Web3(this.getRpcUrl());
|
||||
const signer = web3.eth.accounts.privateKeyToAccount(privateKey);
|
||||
|
@ -375,7 +378,7 @@ export class AptosChain extends Chain {
|
|||
return AptosChain.type;
|
||||
}
|
||||
|
||||
toJson(): any {
|
||||
toJson(): KeyValueConfig {
|
||||
return {
|
||||
id: this.id,
|
||||
wormholeChainName: this.wormholeChainName,
|
||||
|
@ -385,7 +388,7 @@ export class AptosChain extends Chain {
|
|||
};
|
||||
}
|
||||
|
||||
static fromJson(parsed: any): AptosChain {
|
||||
static fromJson(parsed: ChainConfig): AptosChain {
|
||||
if (parsed.type !== AptosChain.type) throw new Error("Invalid type");
|
||||
return new AptosChain(
|
||||
parsed.id,
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { Contract, PriceFeed } from "../base";
|
||||
import { AptosAccount, BCS, TxnBuilderTypes } from "aptos";
|
||||
import { Contract, PriceFeed, PrivateKey, TxResult } from "../base";
|
||||
import { ApiError, AptosAccount, BCS, TxnBuilderTypes } from "aptos";
|
||||
import { AptosChain, Chain } from "../chains";
|
||||
import { DataSource } from "xc_admin_common";
|
||||
import { CoinClient } from "aptos";
|
||||
|
||||
export class AptosContract extends Contract {
|
||||
static type: string = "AptosContract";
|
||||
static type = "AptosContract";
|
||||
|
||||
/**
|
||||
* Given the ids of the pyth state and wormhole state, create a new AptosContract
|
||||
|
@ -23,7 +23,10 @@ export class AptosContract extends Contract {
|
|||
super();
|
||||
}
|
||||
|
||||
static fromJson(chain: Chain, parsed: any): AptosContract {
|
||||
static fromJson(
|
||||
chain: Chain,
|
||||
parsed: { type: string; stateId: string; wormholeStateId: string }
|
||||
): AptosContract {
|
||||
if (parsed.type !== AptosContract.type) throw new Error("Invalid type");
|
||||
if (!(chain instanceof AptosChain))
|
||||
throw new Error(`Wrong chain type ${chain}`);
|
||||
|
@ -31,9 +34,9 @@ export class AptosContract extends Contract {
|
|||
}
|
||||
|
||||
async executeGovernanceInstruction(
|
||||
senderPrivateKey: string,
|
||||
senderPrivateKey: PrivateKey,
|
||||
vaa: Buffer
|
||||
): Promise<any> {
|
||||
): Promise<TxResult> {
|
||||
const txPayload = new TxnBuilderTypes.TransactionPayloadEntryFunction(
|
||||
TxnBuilderTypes.EntryFunction.natural(
|
||||
`${this.stateId}::governance`,
|
||||
|
@ -45,23 +48,28 @@ export class AptosContract extends Contract {
|
|||
return this.sendTransaction(senderPrivateKey, txPayload);
|
||||
}
|
||||
|
||||
private sendTransaction(
|
||||
senderPrivateKey: string,
|
||||
private async sendTransaction(
|
||||
senderPrivateKey: PrivateKey,
|
||||
txPayload: TxnBuilderTypes.TransactionPayloadEntryFunction
|
||||
) {
|
||||
): Promise<TxResult> {
|
||||
const client = this.chain.getClient();
|
||||
const sender = new AptosAccount(
|
||||
new Uint8Array(Buffer.from(senderPrivateKey, "hex"))
|
||||
);
|
||||
return client.generateSignSubmitWaitForTransaction(sender, txPayload, {
|
||||
maxGasAmount: BigInt(30000),
|
||||
});
|
||||
const result = await client.generateSignSubmitWaitForTransaction(
|
||||
sender,
|
||||
txPayload,
|
||||
{
|
||||
maxGasAmount: BigInt(30000),
|
||||
}
|
||||
);
|
||||
return { id: result.hash, info: result };
|
||||
}
|
||||
|
||||
async executeUpdatePriceFeed(
|
||||
senderPrivateKey: string,
|
||||
senderPrivateKey: PrivateKey,
|
||||
vaas: Buffer[]
|
||||
): Promise<any> {
|
||||
): Promise<TxResult> {
|
||||
const txPayload = new TxnBuilderTypes.TransactionPayloadEntryFunction(
|
||||
TxnBuilderTypes.EntryFunction.natural(
|
||||
`${this.stateId}::pyth`,
|
||||
|
@ -93,7 +101,7 @@ export class AptosContract extends Contract {
|
|||
}
|
||||
|
||||
async getBaseUpdateFee() {
|
||||
const data = (await this.findResource("BaseUpdateFee")) as any;
|
||||
const data = (await this.findResource("BaseUpdateFee")) as { fee: string };
|
||||
return { amount: data.fee };
|
||||
}
|
||||
|
||||
|
@ -101,7 +109,12 @@ export class AptosContract extends Contract {
|
|||
return this.chain;
|
||||
}
|
||||
|
||||
private parsePrice(priceInfo: any) {
|
||||
private parsePrice(priceInfo: {
|
||||
expo: { magnitude: string; negative: boolean };
|
||||
price: { magnitude: string; negative: boolean };
|
||||
conf: string;
|
||||
timestamp: string;
|
||||
}) {
|
||||
let expo = priceInfo.expo.magnitude;
|
||||
if (priceInfo.expo.negative) expo = "-" + expo;
|
||||
let price = priceInfo.price.magnitude;
|
||||
|
@ -116,7 +129,9 @@ export class AptosContract extends Contract {
|
|||
|
||||
async getPriceFeed(feedId: string): Promise<PriceFeed | undefined> {
|
||||
const client = this.chain.getClient();
|
||||
const res = (await this.findResource("LatestPriceInfo")) as any;
|
||||
const res = (await this.findResource("LatestPriceInfo")) as {
|
||||
info: { handle: string };
|
||||
};
|
||||
const handle = res.info.handle;
|
||||
try {
|
||||
const priceItemRes = await client.getTableItem(handle, {
|
||||
|
@ -130,15 +145,23 @@ export class AptosContract extends Contract {
|
|||
price: this.parsePrice(priceItemRes.price_feed.price),
|
||||
emaPrice: this.parsePrice(priceItemRes.price_feed.ema_price),
|
||||
};
|
||||
} catch (e: any) {
|
||||
if (e.errorCode === "table_item_not_found") return undefined;
|
||||
} catch (e) {
|
||||
if (e instanceof ApiError && e.errorCode === "table_item_not_found")
|
||||
return undefined;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
async getDataSources(): Promise<DataSource[]> {
|
||||
const data = (await this.findResource("DataSources")) as any;
|
||||
return data.sources.keys.map((source: any) => {
|
||||
const data = (await this.findResource("DataSources")) as {
|
||||
sources: {
|
||||
keys: {
|
||||
emitter_chain: string;
|
||||
emitter_address: { external_address: string };
|
||||
}[];
|
||||
};
|
||||
};
|
||||
return data.sources.keys.map((source) => {
|
||||
return {
|
||||
emitterChain: Number(source.emitter_chain),
|
||||
emitterAddress: source.emitter_address.external_address.replace(
|
||||
|
@ -150,7 +173,12 @@ export class AptosContract extends Contract {
|
|||
}
|
||||
|
||||
async getGovernanceDataSource(): Promise<DataSource> {
|
||||
const data = (await this.findResource("GovernanceDataSource")) as any;
|
||||
const data = (await this.findResource("GovernanceDataSource")) as {
|
||||
source: {
|
||||
emitter_chain: string;
|
||||
emitter_address: { external_address: string };
|
||||
};
|
||||
};
|
||||
return {
|
||||
emitterChain: Number(data.source.emitter_chain),
|
||||
emitterAddress: data.source.emitter_address.external_address.replace(
|
||||
|
@ -163,7 +191,7 @@ export class AptosContract extends Contract {
|
|||
async getLastExecutedGovernanceSequence() {
|
||||
const data = (await this.findResource(
|
||||
"LastExecutedGovernanceSequence"
|
||||
)) as any;
|
||||
)) as { sequence: string };
|
||||
return Number(data.sequence);
|
||||
}
|
||||
|
||||
|
@ -181,7 +209,9 @@ export class AptosContract extends Contract {
|
|||
}
|
||||
|
||||
async getValidTimePeriod() {
|
||||
const data = (await this.findResource("StalePriceThreshold")) as any;
|
||||
const data = (await this.findResource("StalePriceThreshold")) as {
|
||||
threshold_secs: string;
|
||||
};
|
||||
return Number(data.threshold_secs);
|
||||
}
|
||||
|
||||
|
|
|
@ -9,30 +9,29 @@ import {
|
|||
PythWrapperExecutor,
|
||||
PythWrapperQuerier,
|
||||
} from "@pythnetwork/cosmwasm-deploy-tools";
|
||||
import { Coin } from "@cosmjs/stargate";
|
||||
import { CHAINS, DataSource } from "xc_admin_common";
|
||||
import { CosmWasmClient } from "@cosmjs/cosmwasm-stargate";
|
||||
import { Contract } from "../base";
|
||||
import { Contract, PrivateKey, TxResult } from "../base";
|
||||
import { WormholeContract } from "./wormhole";
|
||||
|
||||
/**
|
||||
* 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 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 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 WormholeCosmWasmContract extends WormholeContract {
|
||||
|
@ -44,7 +43,7 @@ export class WormholeCosmWasmContract extends WormholeContract {
|
|||
const chainQuerier = await CosmwasmQuerier.connect(this.chain.endpoint);
|
||||
return (await chainQuerier.getAllContractState({
|
||||
contractAddr: this.address,
|
||||
})) as any;
|
||||
})) as Record<string, string>;
|
||||
}
|
||||
|
||||
async getCurrentGuardianSetIndex(): Promise<number> {
|
||||
|
@ -68,28 +67,31 @@ export class WormholeCosmWasmContract extends WormholeContract {
|
|||
}
|
||||
|
||||
async upgradeGuardianSets(
|
||||
senderPrivateKey: string,
|
||||
senderPrivateKey: PrivateKey,
|
||||
vaa: Buffer
|
||||
): Promise<any> {
|
||||
): Promise<TxResult> {
|
||||
const executor = await this.chain.getExecutor(senderPrivateKey);
|
||||
return executor.executeContract({
|
||||
const result = await executor.executeContract({
|
||||
contractAddr: this.address,
|
||||
msg: {
|
||||
submit_v_a_a: { vaa: vaa.toString("base64") },
|
||||
},
|
||||
});
|
||||
return { id: result.txHash, info: result };
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
emitterChain: Number(chain_id),
|
||||
emitterAddress: Buffer.from(emitter, "base64").toString("hex"),
|
||||
};
|
||||
});
|
||||
return config.config_v1.data_sources.map(
|
||||
({ emitter, chain_id }: { emitter: string; chain_id: string }) => {
|
||||
return {
|
||||
emitterChain: Number(chain_id),
|
||||
emitterAddress: Buffer.from(emitter, "base64").toString("hex"),
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async getGovernanceDataSource(): Promise<DataSource> {
|
||||
|
@ -108,7 +110,10 @@ export class CosmWasmContract extends Contract {
|
|||
super();
|
||||
}
|
||||
|
||||
static fromJson(chain: Chain, parsed: any): CosmWasmContract {
|
||||
static fromJson(
|
||||
chain: Chain,
|
||||
parsed: { type: string; address: string }
|
||||
): CosmWasmContract {
|
||||
if (parsed.type !== CosmWasmContract.type) throw new Error("Invalid type");
|
||||
if (!(chain instanceof CosmWasmChain))
|
||||
throw new Error(`Wrong chain type ${chain}`);
|
||||
|
@ -119,17 +124,16 @@ export class CosmWasmContract extends Contract {
|
|||
return CosmWasmContract.type;
|
||||
}
|
||||
|
||||
//TODO : make deploymentType enum stable | edge
|
||||
static getDeploymentConfig(
|
||||
chain: CosmWasmChain,
|
||||
deploymentType: string,
|
||||
deploymentType: DeploymentType,
|
||||
wormholeContract: string
|
||||
): CosmWasmContract.DeploymentConfig {
|
||||
): DeploymentConfig {
|
||||
return getPythConfig({
|
||||
feeDenom: chain.feeDenom,
|
||||
wormholeChainId: CHAINS[chain.getId() as keyof typeof CHAINS],
|
||||
wormholeChainId: CHAINS[chain.wormholeChainName],
|
||||
wormholeContract,
|
||||
deploymentType: deploymentType as DeploymentType,
|
||||
deploymentType: deploymentType,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -142,11 +146,11 @@ export class CosmWasmContract extends Contract {
|
|||
*/
|
||||
static async storeCode(
|
||||
chain: CosmWasmChain,
|
||||
privateKey: string,
|
||||
privateKey: PrivateKey,
|
||||
wasmPath: string
|
||||
) {
|
||||
const contractBytes = readFileSync(wasmPath);
|
||||
let executor = await chain.getExecutor(privateKey);
|
||||
const executor = await chain.getExecutor(privateKey);
|
||||
return executor.storeCode({ contractBytes });
|
||||
}
|
||||
|
||||
|
@ -160,11 +164,11 @@ export class CosmWasmContract extends Contract {
|
|||
static async initialize(
|
||||
chain: CosmWasmChain,
|
||||
codeId: number,
|
||||
config: CosmWasmContract.DeploymentConfig,
|
||||
privateKey: string
|
||||
config: DeploymentConfig,
|
||||
privateKey: PrivateKey
|
||||
): Promise<CosmWasmContract> {
|
||||
let executor = await chain.getExecutor(privateKey);
|
||||
let result = await executor.instantiateContract({
|
||||
const executor = await chain.getExecutor(privateKey);
|
||||
const result = await executor.instantiateContract({
|
||||
codeId: codeId,
|
||||
instMsg: config,
|
||||
label: "pyth",
|
||||
|
@ -190,10 +194,10 @@ export class CosmWasmContract extends Contract {
|
|||
static async deploy(
|
||||
chain: CosmWasmChain,
|
||||
wormholeContract: string,
|
||||
privateKey: string,
|
||||
privateKey: PrivateKey,
|
||||
wasmPath: string
|
||||
): Promise<CosmWasmContract> {
|
||||
let config = this.getDeploymentConfig(chain, "edge", wormholeContract);
|
||||
const config = this.getDeploymentConfig(chain, "beta", wormholeContract);
|
||||
const { codeId } = await this.storeCode(chain, privateKey, wasmPath);
|
||||
return this.initialize(chain, codeId, config, privateKey);
|
||||
}
|
||||
|
@ -217,7 +221,7 @@ export class CosmWasmContract extends Contract {
|
|||
}
|
||||
|
||||
async getCodeId(): Promise<number> {
|
||||
let result = await this.getWasmContractInfo();
|
||||
const result = await this.getWasmContractInfo();
|
||||
return result.codeId;
|
||||
}
|
||||
|
||||
|
@ -228,10 +232,10 @@ export class CosmWasmContract extends Contract {
|
|||
|
||||
async getConfig() {
|
||||
const chainQuerier = await CosmwasmQuerier.connect(this.chain.endpoint);
|
||||
let allStates = (await chainQuerier.getAllContractState({
|
||||
const allStates = (await chainQuerier.getAllContractState({
|
||||
contractAddr: this.address,
|
||||
})) as any;
|
||||
let config = {
|
||||
})) as Record<string, string>;
|
||||
const config = {
|
||||
config_v1: JSON.parse(allStates["\x00\tconfig_v1"]),
|
||||
contract_version: JSON.parse(allStates["\x00\x10contract_version"]),
|
||||
};
|
||||
|
@ -256,7 +260,7 @@ export class CosmWasmContract extends Contract {
|
|||
}
|
||||
|
||||
async getPriceFeed(feedId: string) {
|
||||
let querier = await this.getQuerier();
|
||||
const querier = await this.getQuerier();
|
||||
try {
|
||||
const response = await querier.getPriceFeed(this.address, feedId);
|
||||
return {
|
||||
|
@ -269,8 +273,8 @@ export class CosmWasmContract extends Contract {
|
|||
}
|
||||
|
||||
equalDataSources(
|
||||
dataSources1: CosmWasmContract.WormholeSource[],
|
||||
dataSources2: CosmWasmContract.WormholeSource[]
|
||||
dataSources1: WormholeSource[],
|
||||
dataSources2: WormholeSource[]
|
||||
): boolean {
|
||||
if (dataSources1.length !== dataSources2.length) return false;
|
||||
for (let i = 0; i < dataSources1.length; i++) {
|
||||
|
@ -290,19 +294,19 @@ export class CosmWasmContract extends Contract {
|
|||
}
|
||||
|
||||
async getDeploymentType(): Promise<string> {
|
||||
let config = await this.getConfig();
|
||||
let wormholeContract = config.config_v1.wormhole_contract;
|
||||
let stableConfig = getPythConfig({
|
||||
const config = await this.getConfig();
|
||||
const wormholeContract = config.config_v1.wormhole_contract;
|
||||
const stableConfig = getPythConfig({
|
||||
feeDenom: this.chain.feeDenom,
|
||||
wormholeChainId: CHAINS[this.chain.getId() as keyof typeof CHAINS],
|
||||
wormholeContract,
|
||||
deploymentType: "stable",
|
||||
});
|
||||
let edgeConfig = getPythConfig({
|
||||
const betaConfig = getPythConfig({
|
||||
feeDenom: this.chain.feeDenom,
|
||||
wormholeChainId: CHAINS[this.chain.getId() as keyof typeof CHAINS],
|
||||
wormholeContract,
|
||||
deploymentType: "edge",
|
||||
deploymentType: "beta",
|
||||
});
|
||||
if (
|
||||
this.equalDataSources(
|
||||
|
@ -314,52 +318,53 @@ export class CosmWasmContract extends Contract {
|
|||
else if (
|
||||
this.equalDataSources(
|
||||
config.config_v1.data_sources,
|
||||
edgeConfig.data_sources
|
||||
betaConfig.data_sources
|
||||
)
|
||||
)
|
||||
return "edge";
|
||||
return "beta";
|
||||
else return "unknown";
|
||||
}
|
||||
|
||||
async executeUpdatePriceFeed(senderPrivateKey: string, vaas: Buffer[]) {
|
||||
async executeUpdatePriceFeed(senderPrivateKey: PrivateKey, vaas: Buffer[]) {
|
||||
const base64Vaas = vaas.map((v) => v.toString("base64"));
|
||||
const fund = await this.getUpdateFee(base64Vaas);
|
||||
let executor = await this.chain.getExecutor(senderPrivateKey);
|
||||
let pythExecutor = new PythWrapperExecutor(executor);
|
||||
return pythExecutor.executeUpdatePriceFeeds({
|
||||
const executor = await this.chain.getExecutor(senderPrivateKey);
|
||||
const pythExecutor = new PythWrapperExecutor(executor);
|
||||
const result = await pythExecutor.executeUpdatePriceFeeds({
|
||||
contractAddr: this.address,
|
||||
vaas: base64Vaas,
|
||||
fund,
|
||||
});
|
||||
return { id: result.txHash, info: result };
|
||||
}
|
||||
|
||||
async executeGovernanceInstruction(privateKey: string, vaa: Buffer) {
|
||||
let executor = await this.chain.getExecutor(privateKey);
|
||||
let pythExecutor = new PythWrapperExecutor(executor);
|
||||
return pythExecutor.executeGovernanceInstruction({
|
||||
async executeGovernanceInstruction(privateKey: PrivateKey, vaa: Buffer) {
|
||||
const executor = await this.chain.getExecutor(privateKey);
|
||||
const pythExecutor = new PythWrapperExecutor(executor);
|
||||
const result = await pythExecutor.executeGovernanceInstruction({
|
||||
contractAddr: this.address,
|
||||
vaa: vaa.toString("base64"),
|
||||
});
|
||||
return { id: result.txHash, info: result };
|
||||
}
|
||||
|
||||
async getWormholeContract(): Promise<WormholeCosmWasmContract> {
|
||||
let config = await this.getConfig();
|
||||
const chainQuerier = await CosmwasmQuerier.connect(this.chain.endpoint);
|
||||
const config = await this.getConfig();
|
||||
const wormholeAddress = config.config_v1.wormhole_contract;
|
||||
return new WormholeCosmWasmContract(this.chain, wormholeAddress);
|
||||
}
|
||||
|
||||
async getUpdateFee(msgs: string[]): Promise<any> {
|
||||
let querier = await this.getQuerier();
|
||||
async getUpdateFee(msgs: string[]): Promise<Coin> {
|
||||
const querier = await this.getQuerier();
|
||||
return querier.getUpdateFee(this.address, msgs);
|
||||
}
|
||||
|
||||
async getBaseUpdateFee(): Promise<any> {
|
||||
async getBaseUpdateFee(): Promise<{ amount: string; denom: string }> {
|
||||
const config = await this.getConfig();
|
||||
return config.config_v1.fee;
|
||||
}
|
||||
|
||||
async getVersion(): Promise<any> {
|
||||
async getVersion(): Promise<string> {
|
||||
const config = await this.getConfig();
|
||||
return config.contract_version;
|
||||
}
|
||||
|
@ -378,8 +383,8 @@ export class CosmWasmContract extends Contract {
|
|||
}
|
||||
|
||||
async getValidTimePeriod() {
|
||||
let client = await CosmWasmClient.connect(this.chain.endpoint);
|
||||
let result = await client.queryContractSmart(
|
||||
const client = await CosmWasmClient.connect(this.chain.endpoint);
|
||||
const result = await client.queryContractSmart(
|
||||
this.address,
|
||||
"get_valid_time_period"
|
||||
);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import Web3 from "web3"; //TODO: decide on using web3 or ethers.js
|
||||
import Web3 from "web3";
|
||||
import PythInterfaceAbi from "@pythnetwork/pyth-sdk-solidity/abis/IPyth.json";
|
||||
import { Contract } from "../base";
|
||||
import { Contract, PrivateKey } from "../base";
|
||||
import { Chain, EvmChain } from "../chains";
|
||||
import { DataSource } from "xc_admin_common";
|
||||
import { WormholeContract } from "./wormhole";
|
||||
|
@ -138,7 +138,7 @@ const EXTENDED_PYTH_ABI = [
|
|||
type: "function",
|
||||
},
|
||||
...PythInterfaceAbi,
|
||||
] as any;
|
||||
] as any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
|
||||
const WORMHOLE_ABI = [
|
||||
{
|
||||
|
@ -211,7 +211,7 @@ const WORMHOLE_ABI = [
|
|||
stateMutability: "view",
|
||||
type: "function",
|
||||
},
|
||||
] as any;
|
||||
] as any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
export class WormholeEvmContract extends WormholeContract {
|
||||
constructor(public chain: EvmChain, public address: string) {
|
||||
super();
|
||||
|
@ -256,7 +256,7 @@ export class WormholeEvmContract extends WormholeContract {
|
|||
}
|
||||
}
|
||||
|
||||
async upgradeGuardianSets(senderPrivateKey: string, vaa: Buffer) {
|
||||
async upgradeGuardianSets(senderPrivateKey: PrivateKey, vaa: Buffer) {
|
||||
const web3 = new Web3(this.chain.getRpcUrl());
|
||||
const { address } = web3.eth.accounts.wallet.add(senderPrivateKey);
|
||||
const wormholeContract = new web3.eth.Contract(WORMHOLE_ABI, this.address);
|
||||
|
@ -267,11 +267,12 @@ export class WormholeEvmContract extends WormholeContract {
|
|||
from: address,
|
||||
gas: 100000000,
|
||||
});
|
||||
return transactionObject.send({
|
||||
const result = await transactionObject.send({
|
||||
from: address,
|
||||
gas: gasEstiamte * GAS_ESTIMATE_MULTIPLIER,
|
||||
gasPrice: await this.chain.getGasPrice(),
|
||||
});
|
||||
return { id: result.transactionHash, info: result };
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -282,7 +283,10 @@ export class EvmContract extends Contract {
|
|||
super();
|
||||
}
|
||||
|
||||
static fromJson(chain: Chain, parsed: any): EvmContract {
|
||||
static fromJson(
|
||||
chain: Chain,
|
||||
parsed: { type: string; address: string }
|
||||
): EvmContract {
|
||||
if (parsed.type !== EvmContract.type) throw new Error("Invalid type");
|
||||
if (!(chain instanceof EvmChain))
|
||||
throw new Error(`Wrong chain type ${chain}`);
|
||||
|
@ -321,7 +325,7 @@ export class EvmContract extends Contract {
|
|||
async getImplementationAddress(): Promise<string> {
|
||||
const web3 = new Web3(this.chain.getRpcUrl());
|
||||
// bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1) according to EIP-1967
|
||||
let storagePosition =
|
||||
const storagePosition =
|
||||
"0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc";
|
||||
let address = await web3.eth.getStorageAt(this.address, storagePosition);
|
||||
address = "0x" + address.slice(26);
|
||||
|
@ -433,7 +437,7 @@ export class EvmContract extends Contract {
|
|||
};
|
||||
}
|
||||
|
||||
async executeUpdatePriceFeed(senderPrivateKey: string, vaas: Buffer[]) {
|
||||
async executeUpdatePriceFeed(senderPrivateKey: PrivateKey, vaas: Buffer[]) {
|
||||
const web3 = new Web3(this.chain.getRpcUrl());
|
||||
const { address } = web3.eth.accounts.wallet.add(senderPrivateKey);
|
||||
const pythContract = new web3.eth.Contract(EXTENDED_PYTH_ABI, this.address);
|
||||
|
@ -448,15 +452,19 @@ export class EvmContract extends Contract {
|
|||
gas: 100000000,
|
||||
value: updateFee,
|
||||
});
|
||||
return transactionObject.send({
|
||||
const result = await transactionObject.send({
|
||||
from: address,
|
||||
value: updateFee,
|
||||
gas: gasEstiamte * GAS_ESTIMATE_MULTIPLIER,
|
||||
gasPrice: await this.chain.getGasPrice(),
|
||||
});
|
||||
return { id: result.transactionHash, info: result };
|
||||
}
|
||||
|
||||
async executeGovernanceInstruction(senderPrivateKey: string, vaa: Buffer) {
|
||||
async executeGovernanceInstruction(
|
||||
senderPrivateKey: PrivateKey,
|
||||
vaa: Buffer
|
||||
) {
|
||||
const web3 = new Web3(this.chain.getRpcUrl());
|
||||
const { address } = web3.eth.accounts.wallet.add(senderPrivateKey);
|
||||
const pythContract = new web3.eth.Contract(EXTENDED_PYTH_ABI, this.address);
|
||||
|
@ -467,11 +475,12 @@ export class EvmContract extends Contract {
|
|||
from: address,
|
||||
gas: 100000000,
|
||||
});
|
||||
return transactionObject.send({
|
||||
const result = await transactionObject.send({
|
||||
from: address,
|
||||
gas: gasEstiamte * GAS_ESTIMATE_MULTIPLIER,
|
||||
gasPrice: await this.chain.getGasPrice(),
|
||||
});
|
||||
return { id: result.transactionHash, info: result };
|
||||
}
|
||||
|
||||
getChain(): EvmChain {
|
||||
|
|
|
@ -9,7 +9,7 @@ import {
|
|||
} from "@mysten/sui.js";
|
||||
import { Chain, SuiChain } from "../chains";
|
||||
import { DataSource } from "xc_admin_common";
|
||||
import { Contract } from "../base";
|
||||
import { Contract, PrivateKey, TxResult } from "../base";
|
||||
import { SuiPythClient } from "@pythnetwork/pyth-sui-js";
|
||||
|
||||
export class SuiContract extends Contract {
|
||||
|
@ -31,7 +31,10 @@ export class SuiContract extends Contract {
|
|||
super();
|
||||
}
|
||||
|
||||
static fromJson(chain: Chain, parsed: any): SuiContract {
|
||||
static fromJson(
|
||||
chain: Chain,
|
||||
parsed: { type: string; stateId: string; wormholeStateId: string }
|
||||
): SuiContract {
|
||||
if (parsed.type !== SuiContract.type) throw new Error("Invalid type");
|
||||
if (!(chain instanceof SuiChain))
|
||||
throw new Error(`Wrong chain type ${chain}`);
|
||||
|
@ -81,7 +84,7 @@ export class SuiContract extends Contract {
|
|||
*/
|
||||
async getPriceTableId(): Promise<ObjectId> {
|
||||
const provider = this.getProvider();
|
||||
let result = await provider.getDynamicFieldObject({
|
||||
const result = await provider.getDynamicFieldObject({
|
||||
parentId: this.stateId,
|
||||
name: {
|
||||
type: "vector<u8>",
|
||||
|
@ -94,7 +97,15 @@ export class SuiContract extends Contract {
|
|||
return result.data.objectId;
|
||||
}
|
||||
|
||||
private async parsePrice(priceInfo: any) {
|
||||
private async parsePrice(priceInfo: {
|
||||
type: string;
|
||||
fields: {
|
||||
expo: { fields: { magnitude: string; negative: boolean } };
|
||||
price: { fields: { magnitude: string; negative: boolean } };
|
||||
conf: string;
|
||||
timestamp: string;
|
||||
};
|
||||
}) {
|
||||
const packageId = await this.getPythPackageId();
|
||||
const expectedType = `${packageId}::price::Price`;
|
||||
if (priceInfo.type !== expectedType) {
|
||||
|
@ -117,7 +128,7 @@ export class SuiContract extends Contract {
|
|||
async getPriceFeedObjectId(feedId: string): Promise<ObjectId | undefined> {
|
||||
const tableId = await this.getPriceTableId();
|
||||
const provider = this.getProvider();
|
||||
let result = await provider.getDynamicFieldObject({
|
||||
const result = await provider.getDynamicFieldObject({
|
||||
parentId: tableId,
|
||||
name: {
|
||||
type: `${await this.getPythPackageId()}::price_identifier::PriceIdentifier`,
|
||||
|
@ -137,9 +148,9 @@ export class SuiContract extends Contract {
|
|||
|
||||
async getPriceFeed(feedId: string) {
|
||||
const provider = this.getProvider();
|
||||
let priceInfoObjectId = await this.getPriceFeedObjectId(feedId);
|
||||
const priceInfoObjectId = await this.getPriceFeedObjectId(feedId);
|
||||
if (!priceInfoObjectId) return undefined;
|
||||
let priceInfo = await provider.getObject({
|
||||
const priceInfo = await provider.getObject({
|
||||
id: priceInfoObjectId,
|
||||
options: { showContent: true },
|
||||
});
|
||||
|
@ -173,7 +184,7 @@ export class SuiContract extends Contract {
|
|||
async executeMigrateInstruction(vaa: Buffer, keypair: Ed25519Keypair) {
|
||||
const tx = new TransactionBlock();
|
||||
const packageId = await this.getPythPackageId();
|
||||
let verificationReceipt = await this.getVaaVerificationReceipt(
|
||||
const verificationReceipt = await this.getVaaVerificationReceipt(
|
||||
tx,
|
||||
packageId,
|
||||
vaa
|
||||
|
@ -187,7 +198,7 @@ export class SuiContract extends Contract {
|
|||
return this.executeTransaction(tx, keypair);
|
||||
}
|
||||
|
||||
async executeUpdatePriceFeed(senderPrivateKey: string, vaas: Buffer[]) {
|
||||
async executeUpdatePriceFeed(): Promise<TxResult> {
|
||||
// We need the feed ids to be able to execute the transaction
|
||||
// it may be possible to get them from the VAA but in batch transactions,
|
||||
// it is also possible to hava fewer feeds that user wants to update compared to
|
||||
|
@ -207,17 +218,20 @@ export class SuiContract extends Contract {
|
|||
senderPrivateKey: string,
|
||||
vaas: Buffer[],
|
||||
feedIds: string[]
|
||||
) {
|
||||
): Promise<TxResult> {
|
||||
const tx = new TransactionBlock();
|
||||
const client = this.getSdkClient();
|
||||
await client.updatePriceFeeds(tx, vaas, feedIds);
|
||||
const keypair = Ed25519Keypair.fromSecretKey(
|
||||
Buffer.from(senderPrivateKey, "hex")
|
||||
);
|
||||
let result = await this.executeTransaction(tx, keypair);
|
||||
return result.digest;
|
||||
const result = await this.executeTransaction(tx, keypair);
|
||||
return { id: result.digest, info: result };
|
||||
}
|
||||
async executeCreatePriceFeed(senderPrivateKey: string, vaas: Buffer[]) {
|
||||
async executeCreatePriceFeed(
|
||||
senderPrivateKey: string,
|
||||
vaas: Buffer[]
|
||||
): Promise<TxResult> {
|
||||
const tx = new TransactionBlock();
|
||||
const client = this.getSdkClient();
|
||||
await client.createPriceFeed(tx, vaas);
|
||||
|
@ -225,17 +239,20 @@ export class SuiContract extends Contract {
|
|||
Buffer.from(senderPrivateKey, "hex")
|
||||
);
|
||||
|
||||
let result = await this.executeTransaction(tx, keypair);
|
||||
return result.digest;
|
||||
const result = await this.executeTransaction(tx, keypair);
|
||||
return { id: result.digest, info: result };
|
||||
}
|
||||
|
||||
async executeGovernanceInstruction(senderPrivateKey: string, vaa: Buffer) {
|
||||
async executeGovernanceInstruction(
|
||||
senderPrivateKey: PrivateKey,
|
||||
vaa: Buffer
|
||||
): Promise<TxResult> {
|
||||
const keypair = Ed25519Keypair.fromSecretKey(
|
||||
Buffer.from(senderPrivateKey, "hex")
|
||||
);
|
||||
const tx = new TransactionBlock();
|
||||
const packageId = await this.getPythPackageId();
|
||||
let verificationReceipt = await this.getVaaVerificationReceipt(
|
||||
const verificationReceipt = await this.getVaaVerificationReceipt(
|
||||
tx,
|
||||
packageId,
|
||||
vaa
|
||||
|
@ -246,7 +263,8 @@ export class SuiContract extends Contract {
|
|||
arguments: [tx.object(this.stateId), verificationReceipt],
|
||||
});
|
||||
|
||||
return this.executeTransaction(tx, keypair);
|
||||
const result = await this.executeTransaction(tx, keypair);
|
||||
return { id: result.digest, info: result };
|
||||
}
|
||||
|
||||
async executeUpgradeInstruction(
|
||||
|
@ -257,7 +275,7 @@ export class SuiContract extends Contract {
|
|||
) {
|
||||
const tx = new TransactionBlock();
|
||||
const packageId = await this.getPythPackageId();
|
||||
let verificationReceipt = await this.getVaaVerificationReceipt(
|
||||
const verificationReceipt = await this.getVaaVerificationReceipt(
|
||||
tx,
|
||||
packageId,
|
||||
vaa
|
||||
|
@ -279,7 +297,8 @@ export class SuiContract extends Contract {
|
|||
target: `${packageId}::contract_upgrade::commit_upgrade`,
|
||||
arguments: [tx.object(this.stateId), upgradeReceipt],
|
||||
});
|
||||
return this.executeTransaction(tx, keypair);
|
||||
const result = await this.executeTransaction(tx, keypair);
|
||||
return { id: result.digest, info: result };
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -297,7 +316,7 @@ export class SuiContract extends Contract {
|
|||
) {
|
||||
const wormholePackageId = await this.getWormholePackageId();
|
||||
|
||||
let [verifiedVAA] = tx.moveCall({
|
||||
const [verifiedVAA] = tx.moveCall({
|
||||
target: `${wormholePackageId}::vaa::parse_and_verify`,
|
||||
arguments: [
|
||||
tx.object(this.wormholeStateId),
|
||||
|
@ -306,7 +325,7 @@ export class SuiContract extends Contract {
|
|||
],
|
||||
});
|
||||
|
||||
let [verificationReceipt] = tx.moveCall({
|
||||
const [verificationReceipt] = tx.moveCall({
|
||||
target: `${packageId}::governance::verify_vaa`,
|
||||
arguments: [tx.object(this.stateId), verifiedVAA],
|
||||
});
|
||||
|
@ -325,7 +344,7 @@ export class SuiContract extends Contract {
|
|||
keypair: Ed25519Keypair
|
||||
) {
|
||||
const provider = this.getProvider();
|
||||
let txBlock = {
|
||||
const txBlock = {
|
||||
transactionBlock: tx,
|
||||
options: {
|
||||
showEffects: true,
|
||||
|
@ -333,7 +352,7 @@ export class SuiContract extends Contract {
|
|||
},
|
||||
};
|
||||
const wallet = new RawSigner(keypair, provider);
|
||||
let gasCost = await wallet.getGasCostEstimation(txBlock);
|
||||
const gasCost = await wallet.getGasCostEstimation(txBlock);
|
||||
tx.setGasBudget(gasCost * BigInt(2));
|
||||
return wallet.signAndExecuteTransactionBlock(txBlock);
|
||||
}
|
||||
|
@ -345,7 +364,7 @@ export class SuiContract extends Contract {
|
|||
|
||||
async getDataSources(): Promise<DataSource[]> {
|
||||
const provider = this.getProvider();
|
||||
let result = await provider.getDynamicFieldObject({
|
||||
const result = await provider.getDynamicFieldObject({
|
||||
parentId: this.stateId,
|
||||
name: {
|
||||
type: "vector<u8>",
|
||||
|
@ -361,7 +380,14 @@ export class SuiContract extends Contract {
|
|||
throw new Error("Data Sources type mismatch");
|
||||
}
|
||||
return result.data.content.fields.value.fields.keys.map(
|
||||
({ fields }: any) => {
|
||||
({
|
||||
fields,
|
||||
}: {
|
||||
fields: {
|
||||
emitter_address: { fields: { value: { fields: { data: string } } } };
|
||||
emitter_chain: string;
|
||||
};
|
||||
}) => {
|
||||
return {
|
||||
emitterChain: Number(fields.emitter_chain),
|
||||
emitterAddress: Buffer.from(
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { PrivateKey, TxResult } from "../base";
|
||||
|
||||
export abstract class WormholeContract {
|
||||
abstract getCurrentGuardianSetIndex(): Promise<number>;
|
||||
|
||||
|
@ -12,15 +14,15 @@ export abstract class WormholeContract {
|
|||
* @param vaa
|
||||
*/
|
||||
abstract upgradeGuardianSets(
|
||||
senderPrivateKey: string,
|
||||
senderPrivateKey: PrivateKey,
|
||||
vaa: Buffer
|
||||
): Promise<any>;
|
||||
): Promise<TxResult>;
|
||||
|
||||
/**
|
||||
* Upgrades the guardian set of this contract with the 3 pre-configured VAAs for mainnet assuming this is a mainnet contract
|
||||
* @param senderPrivateKey
|
||||
*/
|
||||
async syncMainnetGuardianSets(senderPrivateKey: string) {
|
||||
async syncMainnetGuardianSets(senderPrivateKey: PrivateKey) {
|
||||
const MAINNET_UPGRADE_VAAS = [
|
||||
"010000000001007ac31b282c2aeeeb37f3385ee0de5f8e421d30b9e5ae8ba3d4375c1c77a86e77159bb697d9c456d6f8c02d22a94b1279b65b0d6a9957e7d3857423845ac758e300610ac1d2000000030001000000000000000000000000000000000000000000000000000000000000000400000000000005390000000000000000000000000000000000000000000000000000000000436f7265020000000000011358cc3ae5c097b213ce3c81979e1b9f9570746aa5ff6cb952589bde862c25ef4392132fb9d4a42157114de8460193bdf3a2fcf81f86a09765f4762fd1107a0086b32d7a0977926a205131d8731d39cbeb8c82b2fd82faed2711d59af0f2499d16e726f6b211b39756c042441be6d8650b69b54ebe715e234354ce5b4d348fb74b958e8966e2ec3dbd4958a7cdeb5f7389fa26941519f0863349c223b73a6ddee774a3bf913953d695260d88bc1aa25a4eee363ef0000ac0076727b35fbea2dac28fee5ccb0fea768eaf45ced136b9d9e24903464ae889f5c8a723fc14f93124b7c738843cbb89e864c862c38cddcccf95d2cc37a4dc036a8d232b48f62cdd4731412f4890da798f6896a3331f64b48c12d1d57fd9cbe7081171aa1be1d36cafe3867910f99c09e347899c19c38192b6e7387ccd768277c17dab1b7a5027c0b3cf178e21ad2e77ae06711549cfbb1f9c7a9d8096e85e1487f35515d02a92753504a8d75471b9f49edb6fbebc898f403e4773e95feb15e80c9a99c8348d",
|
||||
"01000000010d0012e6b39c6da90c5dfd3c228edbb78c7a4c97c488ff8a346d161a91db067e51d638c17216f368aa9bdf4836b8645a98018ca67d2fec87d769cabfdf2406bf790a0002ef42b288091a670ef3556596f4f47323717882881eaf38e03345078d07a156f312b785b64dae6e9a87e3d32872f59cb1931f728cecf511762981baf48303668f0103cef2616b84c4e511ff03329e0853f1bd7ee9ac5ba71d70a4d76108bddf94f69c2a8a84e4ee94065e8003c334e899184943634e12043d0dda78d93996da073d190104e76d166b9dac98f602107cc4b44ac82868faf00b63df7d24f177aa391e050902413b71046434e67c770b19aecdf7fce1d1435ea0be7262e3e4c18f50ddc8175c0105d9450e8216d741e0206a50f93b750a47e0a258b80eb8fed1314cc300b3d905092de25cd36d366097b7103ae2d184121329ba3aa2d7c6cc53273f11af14798110010687477c8deec89d36a23e7948feb074df95362fc8dcbd8ae910ac556a1dee1e755c56b9db5d710c940938ed79bc1895a3646523a58bc55f475a23435a373ecfdd0107fb06734864f79def4e192497362513171530daea81f07fbb9f698afe7e66c6d44db21323144f2657d4a5386a954bb94eef9f64148c33aef6e477eafa2c5c984c01088769e82216310d1827d9bd48645ec23e90de4ef8a8de99e2d351d1df318608566248d80cdc83bdcac382b3c30c670352be87f9069aab5037d0b747208eae9c650109e9796497ff9106d0d1c62e184d83716282870cef61a1ee13d6fc485b521adcce255c96f7d1bca8d8e7e7d454b65783a830bddc9d94092091a268d311ecd84c26010c468c9fb6d41026841ff9f8d7368fa309d4dbea3ea4bbd2feccf94a92cc8a20a226338a8e2126cd16f70eaf15b4fc9be2c3fa19def14e071956a605e9d1ac4162010e23fcb6bd445b7c25afb722250c1acbc061ed964ba9de1326609ae012acdfb96942b2a102a2de99ab96327859a34a2b49a767dbdb62e0a1fb26af60fe44fd496a00106bb0bac77ac68b347645f2fb1ad789ea9bd76fb9b2324f25ae06f97e65246f142df717f662e73948317182c62ce87d79c73def0dba12e5242dfc038382812cfe00126da03c5e56cb15aeeceadc1e17a45753ab4dc0ec7bf6a75ca03143ed4a294f6f61bc3f478a457833e43084ecd7c985bf2f55a55f168aac0e030fc49e845e497101626e9d9a5d9e343f00010000000000000000000000000000000000000000000000000000000000000004c1759167c43f501c2000000000000000000000000000000000000000000000000000000000436f7265020000000000021358cc3ae5c097b213ce3c81979e1b9f9570746aa5ff6cb952589bde862c25ef4392132fb9d4a42157114de8460193bdf3a2fcf81f86a09765f4762fd1107a0086b32d7a0977926a205131d8731d39cbeb8c82b2fd82faed2711d59af0f2499d16e726f6b211b39756c042441be6d8650b69b54ebe715e234354ce5b4d348fb74b958e8966e2ec3dbd4958a7cd66b9590e1c41e0b226937bf9217d1d67fd4e91f574a3bf913953d695260d88bc1aa25a4eee363ef0000ac0076727b35fbea2dac28fee5ccb0fea768eaf45ced136b9d9e24903464ae889f5c8a723fc14f93124b7c738843cbb89e864c862c38cddcccf95d2cc37a4dc036a8d232b48f62cdd4731412f4890da798f6896a3331f64b48c12d1d57fd9cbe7081171aa1be1d36cafe3867910f99c09e347899c19c38192b6e7387ccd768277c17dab1b7a5027c0b3cf178e21ad2e77ae06711549cfbb1f9c7a9d8096e85e1487f35515d02a92753504a8d75471b9f49edb6fbebc898f403e4773e95feb15e80c9a99c8348d",
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import { parseVaa } from "@certusone/wormhole-sdk";
|
||||
import { decodeGovernancePayload } from "xc_admin_common";
|
||||
import { DefaultStore } from "./store";
|
||||
import { PrivateKey } from "./base";
|
||||
|
||||
/**
|
||||
* A general executor that tries to find any contract that can execute a given VAA and executes it
|
||||
* @param senderPrivateKey the private key to execute the governance instruction with
|
||||
* @param vaa the VAA to execute
|
||||
*/
|
||||
export async function executeVaa(senderPrivateKey: string, vaa: Buffer) {
|
||||
export async function executeVaa(senderPrivateKey: PrivateKey, vaa: Buffer) {
|
||||
const parsedVaa = parseVaa(vaa);
|
||||
const action = decodeGovernancePayload(parsedVaa.payload);
|
||||
if (!action) return; //TODO: handle other actions
|
||||
|
|
|
@ -31,7 +31,7 @@ import {
|
|||
deriveFeeCollectorKey,
|
||||
deriveWormholeBridgeDataKey,
|
||||
} from "@certusone/wormhole-sdk/lib/cjs/solana/wormhole";
|
||||
import { Storable } from "./base";
|
||||
import { KeyValueConfig, Storable } from "./base";
|
||||
|
||||
class InvalidTransactionError extends Error {
|
||||
constructor(message: string) {
|
||||
|
@ -75,8 +75,8 @@ export class SubmittedWormholeMessage {
|
|||
)
|
||||
);
|
||||
|
||||
const wormholeAddress =
|
||||
WORMHOLE_ADDRESS[cluster as keyof typeof WORMHOLE_ADDRESS]!;
|
||||
const wormholeAddress = WORMHOLE_ADDRESS[cluster];
|
||||
if (!wormholeAddress) throw new Error(`Invalid cluster ${cluster}`);
|
||||
let emitter: PublicKey | undefined = undefined;
|
||||
|
||||
let allInstructions: (ParsedInstruction | PartiallyDecodedInstruction)[] =
|
||||
|
@ -103,10 +103,10 @@ export class SubmittedWormholeMessage {
|
|||
* 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];
|
||||
async fetchVaa(waitingSeconds = 1): Promise<Buffer> {
|
||||
const rpcUrl = WORMHOLE_API_ENDPOINT[this.cluster];
|
||||
|
||||
let startTime = Date.now();
|
||||
const startTime = Date.now();
|
||||
while (Date.now() - startTime < waitingSeconds * 1000) {
|
||||
const response = await fetch(
|
||||
`${rpcUrl}/v1/signed_vaa/1/${this.emitter.toBuffer().toString("hex")}/${
|
||||
|
@ -158,12 +158,12 @@ export class WormholeEmitter {
|
|||
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 = {
|
||||
const wormholeAddress = WORMHOLE_ADDRESS[this.cluster];
|
||||
if (!wormholeAddress) throw new Error(`Invalid cluster ${this.cluster}`);
|
||||
const kp = Keypair.generate();
|
||||
const feeCollector = deriveFeeCollectorKey(wormholeAddress);
|
||||
const emitter = this.wallet.publicKey;
|
||||
const accounts = {
|
||||
bridge: deriveWormholeBridgeDataKey(wormholeAddress),
|
||||
message: kp.publicKey,
|
||||
emitter: emitter,
|
||||
|
@ -230,7 +230,7 @@ export class WormholeMultiSigTransaction {
|
|||
this.cluster
|
||||
)
|
||||
);
|
||||
} catch (e: any) {
|
||||
} catch (e) {
|
||||
if (!(e instanceof InvalidTransactionError)) throw e;
|
||||
}
|
||||
}
|
||||
|
@ -240,7 +240,7 @@ export class WormholeMultiSigTransaction {
|
|||
}
|
||||
|
||||
export class Vault extends Storable {
|
||||
static type: string = "vault";
|
||||
static type = "vault";
|
||||
key: PublicKey;
|
||||
squad?: SquadsMesh;
|
||||
cluster: PythCluster;
|
||||
|
@ -255,7 +255,11 @@ export class Vault extends Storable {
|
|||
return Vault.type;
|
||||
}
|
||||
|
||||
static fromJson(parsed: any): Vault {
|
||||
static fromJson(parsed: {
|
||||
type: string;
|
||||
key: string;
|
||||
cluster: string;
|
||||
}): Vault {
|
||||
if (parsed.type !== Vault.type) throw new Error("Invalid type");
|
||||
return new Vault(parsed.key, parsed.cluster);
|
||||
}
|
||||
|
@ -264,7 +268,7 @@ export class Vault extends Storable {
|
|||
return `${this.cluster}_${this.key.toString()}`;
|
||||
}
|
||||
|
||||
toJson(): any {
|
||||
toJson(): KeyValueConfig {
|
||||
return {
|
||||
key: this.key.toString(),
|
||||
cluster: this.cluster,
|
||||
|
|
|
@ -12,5 +12,6 @@ repl.evalCode(
|
|||
"import { WormholeEvmContract, EvmContract } from './src/contracts/evm';" +
|
||||
"import { AptosContract } from './src/contracts/aptos';" +
|
||||
"import { DefaultStore } from './src/store';" +
|
||||
"import { toPrivateKey } from './src/base';" +
|
||||
"DefaultStore"
|
||||
);
|
||||
|
|
|
@ -53,7 +53,7 @@ export class Store {
|
|||
}
|
||||
|
||||
loadAllChains() {
|
||||
let allChainClasses = {
|
||||
const allChainClasses = {
|
||||
[CosmWasmChain.type]: CosmWasmChain,
|
||||
[SuiChain.type]: SuiChain,
|
||||
[EvmChain.type]: EvmChain,
|
||||
|
@ -61,10 +61,10 @@ export class Store {
|
|||
};
|
||||
|
||||
this.getYamlFiles(`${this.path}/chains/`).forEach((yamlFile) => {
|
||||
let parsedArray = parse(readFileSync(yamlFile, "utf-8"));
|
||||
const parsedArray = parse(readFileSync(yamlFile, "utf-8"));
|
||||
for (const parsed of parsedArray) {
|
||||
if (allChainClasses[parsed.type] === undefined) return;
|
||||
let chain = allChainClasses[parsed.type].fromJson(parsed);
|
||||
const chain = allChainClasses[parsed.type].fromJson(parsed);
|
||||
if (this.chains[chain.getId()])
|
||||
throw new Error(`Multiple chains with id ${chain.getId()} found`);
|
||||
this.chains[chain.getId()] = chain;
|
||||
|
@ -73,7 +73,7 @@ export class Store {
|
|||
}
|
||||
|
||||
saveAllContracts() {
|
||||
let contractsByType: Record<string, Contract[]> = {};
|
||||
const contractsByType: Record<string, Contract[]> = {};
|
||||
for (const contract of Object.values(this.contracts)) {
|
||||
if (!contractsByType[contract.getType()]) {
|
||||
contractsByType[contract.getType()] = [];
|
||||
|
@ -89,7 +89,7 @@ export class Store {
|
|||
}
|
||||
|
||||
saveAllChains() {
|
||||
let chainsByType: Record<string, Chain[]> = {};
|
||||
const chainsByType: Record<string, Chain[]> = {};
|
||||
for (const chain of Object.values(this.chains)) {
|
||||
if (!chainsByType[chain.getType()]) {
|
||||
chainsByType[chain.getType()] = [];
|
||||
|
@ -105,20 +105,20 @@ export class Store {
|
|||
}
|
||||
|
||||
loadAllContracts() {
|
||||
let allContractClasses = {
|
||||
const allContractClasses = {
|
||||
[CosmWasmContract.type]: CosmWasmContract,
|
||||
[SuiContract.type]: SuiContract,
|
||||
[EvmContract.type]: EvmContract,
|
||||
[AptosContract.type]: AptosContract,
|
||||
};
|
||||
this.getYamlFiles(`${this.path}/contracts/`).forEach((yamlFile) => {
|
||||
let parsedArray = parse(readFileSync(yamlFile, "utf-8"));
|
||||
const parsedArray = parse(readFileSync(yamlFile, "utf-8"));
|
||||
for (const parsed of parsedArray) {
|
||||
if (allContractClasses[parsed.type] === undefined) return;
|
||||
if (!this.chains[parsed.chain])
|
||||
throw new Error(`Chain ${parsed.chain} not found`);
|
||||
const chain = this.chains[parsed.chain];
|
||||
let chainContract = allContractClasses[parsed.type].fromJson(
|
||||
const chainContract = allContractClasses[parsed.type].fromJson(
|
||||
chain,
|
||||
parsed
|
||||
);
|
||||
|
@ -133,7 +133,7 @@ export class Store {
|
|||
|
||||
loadAllVaults() {
|
||||
this.getYamlFiles(`${this.path}/vaults/`).forEach((yamlFile) => {
|
||||
let parsedArray = parse(readFileSync(yamlFile, "utf-8"));
|
||||
const parsedArray = parse(readFileSync(yamlFile, "utf-8"));
|
||||
for (const parsed of parsedArray) {
|
||||
if (parsed.type !== Vault.type) return;
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ export function getChainIdsForStableDeployment(): ChainId[] {
|
|||
];
|
||||
}
|
||||
|
||||
export function getChainIdsForEdgeDeployment(): ChainId[] {
|
||||
export function getChainIdsForBetaDeployment(): ChainId[] {
|
||||
return [
|
||||
ChainId.INJECTIVE_TESTNET,
|
||||
ChainId.OSMOSIS_TESTNET_5,
|
||||
|
@ -28,7 +28,7 @@ export function getChainIdsForEdgeDeployment(): ChainId[] {
|
|||
];
|
||||
}
|
||||
|
||||
export type DeploymentType = "stable" | "edge";
|
||||
export type DeploymentType = "stable" | "beta";
|
||||
|
||||
// We have released the compile contacts on github. If a chain needs some specific
|
||||
// feature in a contract, a version of the contract with that specific features is
|
||||
|
|
|
@ -12,7 +12,7 @@ import { Pipeline } from "./pipeline";
|
|||
import {
|
||||
DeploymentType,
|
||||
WORMHOLE_CONTRACT_VERSION,
|
||||
getChainIdsForEdgeDeployment,
|
||||
getChainIdsForBetaDeployment,
|
||||
getChainIdsForStableDeployment,
|
||||
getContractBytesDict,
|
||||
getPythInstantiateFileName,
|
||||
|
@ -35,7 +35,7 @@ const argv = yargs(hideBin(process.argv))
|
|||
.option("deploy", {
|
||||
type: "string",
|
||||
desc: "Execute this script for the given networks.",
|
||||
choices: ["edge", "stable"],
|
||||
choices: ["beta", "stable"],
|
||||
demandOption: "Please provide the deployment type",
|
||||
})
|
||||
.help()
|
||||
|
@ -48,7 +48,7 @@ async function run() {
|
|||
if (argv.deploy === "stable") {
|
||||
chainIds = getChainIdsForStableDeployment();
|
||||
} else {
|
||||
chainIds = getChainIdsForEdgeDeployment();
|
||||
chainIds = getChainIdsForBetaDeployment();
|
||||
}
|
||||
|
||||
// get the wasm code from github
|
||||
|
|
|
@ -8,7 +8,7 @@ import { Pipeline } from "./pipeline";
|
|||
import { PythWrapperExecutor, PythWrapperQuerier } from "./pyth-wrapper";
|
||||
import {
|
||||
DeploymentType,
|
||||
getChainIdsForEdgeDeployment,
|
||||
getChainIdsForBetaDeployment,
|
||||
getChainIdsForStableDeployment,
|
||||
getPythContractAddress,
|
||||
getTestPythContractFileName,
|
||||
|
@ -29,7 +29,7 @@ const argv = yargs(hideBin(process.argv))
|
|||
.option("deploy", {
|
||||
type: "string",
|
||||
desc: "test the following deployment type.",
|
||||
choices: ["stable", "edge"],
|
||||
choices: ["stable", "beta"],
|
||||
demandOption: "Please provide the deployment type",
|
||||
})
|
||||
.help()
|
||||
|
@ -42,7 +42,7 @@ async function run() {
|
|||
if (argv.deploy === "stable") {
|
||||
chainIds = getChainIdsForStableDeployment();
|
||||
} else {
|
||||
chainIds = getChainIdsForEdgeDeployment();
|
||||
chainIds = getChainIdsForBetaDeployment();
|
||||
}
|
||||
|
||||
for (let chainId of chainIds) {
|
||||
|
|
|
@ -12,7 +12,7 @@ import {
|
|||
import { Pipeline } from "./pipeline";
|
||||
import {
|
||||
DeploymentType,
|
||||
getChainIdsForEdgeDeployment,
|
||||
getChainIdsForBetaDeployment,
|
||||
getChainIdsForStableDeployment,
|
||||
getWormholeFileName,
|
||||
hexToBase64,
|
||||
|
@ -33,7 +33,7 @@ const argv = yargs(hideBin(process.argv))
|
|||
.option("deploy", {
|
||||
type: "string",
|
||||
desc: "Execute this script for the given deployment type.",
|
||||
choices: ["stable", "edge"],
|
||||
choices: ["stable", "beta"],
|
||||
demandOption: "Please provide the deployment type",
|
||||
})
|
||||
.help()
|
||||
|
@ -60,7 +60,7 @@ async function run() {
|
|||
if (argv.deploy === "stable") {
|
||||
chainIds = getChainIdsForStableDeployment();
|
||||
} else {
|
||||
chainIds = getChainIdsForEdgeDeployment();
|
||||
chainIds = getChainIdsForBetaDeployment();
|
||||
}
|
||||
|
||||
for (let chainId of chainIds) {
|
||||
|
|
|
@ -16,7 +16,7 @@ function convertChainId(number) {
|
|||
function getDefaultConfig(chainName) {
|
||||
const chain = DefaultStore.chains[chainName];
|
||||
const { dataSources, governanceDataSource, wormholeConfig } =
|
||||
getDefaultDeploymentConfig(chain.isMainnet() ? "stable" : "edge");
|
||||
getDefaultDeploymentConfig(chain.isMainnet() ? "stable" : "beta");
|
||||
|
||||
const emitterChainIds = dataSources.map((dataSource) =>
|
||||
convertChainId(dataSource.emitterChain)
|
||||
|
|
|
@ -182,7 +182,7 @@ yargs(hideBin(process.argv))
|
|||
provider
|
||||
);
|
||||
const result = await publishPackage(wallet, argv.path);
|
||||
const deploymentType = chain.isMainnet() ? "stable" : "edge";
|
||||
const deploymentType = chain.isMainnet() ? "stable" : "beta";
|
||||
const config = getDefaultDeploymentConfig(deploymentType);
|
||||
await initPyth(
|
||||
wallet,
|
||||
|
|
Loading…
Reference in New Issue