Forráskód Böngészése

Merge branch 'main' into sui-scripts-cleanup

Amin Moghaddam 2 éve
szülő
commit
a60a05f118
100 módosított fájl, 1705 hozzáadás és 547 törlés
  1. 2 6
      .github/workflows/ethereum-contract.yml
  2. 5 0
      .github/workflows/pre-commit.yml
  3. 1 1
      .pre-commit-config.yaml
  4. 0 1
      contract_manager/.gitignore
  5. 2 1
      contract_manager/package.json
  6. 102 0
      contract_manager/scripts/check_proposal.ts
  7. 5 5
      contract_manager/scripts/deploy_cosmwasm.ts
  8. 105 0
      contract_manager/scripts/sync_governance_vaas.ts
  9. 41 0
      contract_manager/scripts/upload_cosmwasm.ts
  10. 0 103
      contract_manager/src/aptos.ts
  11. 33 8
      contract_manager/src/base.ts
  12. 187 81
      contract_manager/src/chains.ts
  13. 190 0
      contract_manager/src/contracts/aptos.ts
  14. 76 65
      contract_manager/src/contracts/cosmwasm.ts
  15. 438 0
      contract_manager/src/contracts/evm.ts
  16. 53 19
      contract_manager/src/contracts/sui.ts
  17. 0 183
      contract_manager/src/evm.ts
  18. 55 13
      contract_manager/src/governance.ts
  19. 6 6
      contract_manager/src/shell.ts
  20. 16 9
      contract_manager/src/store.ts
  21. 4 31
      contract_manager/src/test.ts
  22. 5 0
      contract_manager/store/chains/AptosChain/aptos_mainnet.yaml
  23. 2 0
      contract_manager/store/chains/AptosChain/aptos_testnet.yaml
  24. 5 0
      contract_manager/store/chains/CosmWasmChain/injective.yaml
  25. 5 0
      contract_manager/store/chains/CosmWasmChain/injective_testnet.yaml
  26. 2 0
      contract_manager/store/chains/CosmWasmChain/juno_testnet.yaml
  27. 2 0
      contract_manager/store/chains/CosmWasmChain/neutron.yaml
  28. 4 2
      contract_manager/store/chains/CosmWasmChain/neutron_testnet_pion_1.yaml
  29. 9 0
      contract_manager/store/chains/CosmWasmChain/osmosis.yaml
  30. 2 0
      contract_manager/store/chains/CosmWasmChain/osmosis_testnet_5.yaml
  31. 2 0
      contract_manager/store/chains/CosmWasmChain/sei_pacific_1.yaml
  32. 3 1
      contract_manager/store/chains/CosmWasmChain/sei_testnet_atlantic_2.yaml
  33. 0 3
      contract_manager/store/chains/EVMChain/arbitrum_testnet.yaml
  34. 0 3
      contract_manager/store/chains/EVMChain/cronos.yaml
  35. 0 3
      contract_manager/store/chains/EVMChain/cronos_testnet.yaml
  36. 6 0
      contract_manager/store/chains/EvmChain/arbitrum.yaml
  37. 6 0
      contract_manager/store/chains/EvmChain/arbitrum_testnet.yaml
  38. 6 0
      contract_manager/store/chains/EvmChain/aurora.yaml
  39. 6 0
      contract_manager/store/chains/EvmChain/aurora_testnet.yaml
  40. 6 0
      contract_manager/store/chains/EvmChain/avalanche.yaml
  41. 6 0
      contract_manager/store/chains/EvmChain/base.yaml
  42. 6 0
      contract_manager/store/chains/EvmChain/base_goerli.yaml
  43. 6 0
      contract_manager/store/chains/EvmChain/bnb.yaml
  44. 6 0
      contract_manager/store/chains/EvmChain/bnb_testnet.yaml
  45. 6 0
      contract_manager/store/chains/EvmChain/canto.yaml
  46. 6 0
      contract_manager/store/chains/EvmChain/canto_testnet.yaml
  47. 6 0
      contract_manager/store/chains/EvmChain/celo.yaml
  48. 6 0
      contract_manager/store/chains/EvmChain/celo_alfajores_testnet.yaml
  49. 6 0
      contract_manager/store/chains/EvmChain/chiado.yaml
  50. 6 0
      contract_manager/store/chains/EvmChain/conflux_espace.yaml
  51. 6 0
      contract_manager/store/chains/EvmChain/conflux_espace_testnet.yaml
  52. 6 0
      contract_manager/store/chains/EvmChain/cronos.yaml
  53. 6 0
      contract_manager/store/chains/EvmChain/cronos_testnet.yaml
  54. 6 0
      contract_manager/store/chains/EvmChain/eos.yaml
  55. 6 0
      contract_manager/store/chains/EvmChain/eos_testnet.yaml
  56. 6 0
      contract_manager/store/chains/EvmChain/ethereum.yaml
  57. 6 0
      contract_manager/store/chains/EvmChain/evmos.yaml
  58. 6 0
      contract_manager/store/chains/EvmChain/evmos_testnet.yaml
  59. 6 0
      contract_manager/store/chains/EvmChain/fantom.yaml
  60. 6 0
      contract_manager/store/chains/EvmChain/fantom_testnet.yaml
  61. 6 0
      contract_manager/store/chains/EvmChain/fuji.yaml
  62. 6 0
      contract_manager/store/chains/EvmChain/gnosis.yaml
  63. 6 0
      contract_manager/store/chains/EvmChain/goerli.yaml
  64. 6 0
      contract_manager/store/chains/EvmChain/kava.yaml
  65. 6 0
      contract_manager/store/chains/EvmChain/kava_testnet.yaml
  66. 6 0
      contract_manager/store/chains/EvmChain/kcc.yaml
  67. 6 0
      contract_manager/store/chains/EvmChain/kcc_testnet.yaml
  68. 6 0
      contract_manager/store/chains/EvmChain/linea.yaml
  69. 6 0
      contract_manager/store/chains/EvmChain/linea_goerli.yaml
  70. 6 0
      contract_manager/store/chains/EvmChain/mantle.yaml
  71. 6 0
      contract_manager/store/chains/EvmChain/mantle_testnet.yaml
  72. 6 0
      contract_manager/store/chains/EvmChain/meter.yaml
  73. 6 0
      contract_manager/store/chains/EvmChain/meter_testnet.yaml
  74. 6 0
      contract_manager/store/chains/EvmChain/mumbai.yaml
  75. 6 0
      contract_manager/store/chains/EvmChain/neon.yaml
  76. 6 0
      contract_manager/store/chains/EvmChain/neon_devnet.yaml
  77. 6 0
      contract_manager/store/chains/EvmChain/optimism.yaml
  78. 6 0
      contract_manager/store/chains/EvmChain/optimism_goerli.yaml
  79. 6 0
      contract_manager/store/chains/EvmChain/polygon.yaml
  80. 6 0
      contract_manager/store/chains/EvmChain/polygon_zkevm.yaml
  81. 6 0
      contract_manager/store/chains/EvmChain/polygon_zkevm_testnet.yaml
  82. 6 0
      contract_manager/store/chains/EvmChain/shimmer_testnet.yaml
  83. 6 0
      contract_manager/store/chains/EvmChain/syndr_nitro_testnet.yaml
  84. 6 0
      contract_manager/store/chains/EvmChain/wemix.yaml
  85. 6 0
      contract_manager/store/chains/EvmChain/wemix_testnet.yaml
  86. 6 0
      contract_manager/store/chains/EvmChain/zksync.yaml
  87. 6 0
      contract_manager/store/chains/EvmChain/zksync_goerli.yaml
  88. 2 0
      contract_manager/store/chains/SuiChain/sui_devnet.yaml
  89. 2 0
      contract_manager/store/chains/SuiChain/sui_mainnet.yaml
  90. 2 0
      contract_manager/store/chains/SuiChain/sui_testnet.yaml
  91. 4 0
      contract_manager/store/contracts/AptosContract/aptos_mainnet_0xbd6d205f2aa288baa71270e66716d3d1bafe173ab9f312de4e9dd761ddef5409.yaml
  92. 0 0
      contract_manager/store/contracts/AptosContract/aptos_testnet_0xbd6d205f2aa288baa71270e66716d3d1bafe173ab9f312de4e9dd761ddef5409.yaml
  93. 3 0
      contract_manager/store/contracts/CosmWasmContract/injective_inj12j43nf2f0qumnt2zrrmpvnsqgzndxefujlvr08.yaml
  94. 3 0
      contract_manager/store/contracts/CosmWasmContract/injective_testnet_inj18hckkzqf47mdhd734g6papk6wj20y24rm43sk9.yaml
  95. 3 0
      contract_manager/store/contracts/CosmWasmContract/injective_testnet_inj18rlflp3735h25jmjx97d22c72sxk260amdjxlu.yaml
  96. 3 0
      contract_manager/store/contracts/CosmWasmContract/juno_testnet_juno1eacsrua27njc35pxz37y97gmcjs899t59f8pf0rkejjyvtmhws5q6lxsdd.yaml
  97. 3 0
      contract_manager/store/contracts/CosmWasmContract/neutron_testnet_pion_1_neutron15ldst8t80982akgr8w8ekcytejzkmfpgdkeq4xgtge48qs7435jqp87u3t.yaml
  98. 3 0
      contract_manager/store/contracts/CosmWasmContract/neutron_testnet_pion_1_neutron16zwrmx3zgggmxhzau86xfycm42cr4sj888hdvzsxya3qarp6zhhqzhlkvz.yaml
  99. 0 3
      contract_manager/store/contracts/CosmWasmContract/neutron_testnet_pion_1_neutron1xxmcu6wxgawjlajx8jalyk9cxsudnygjg0tvjesfyurh4utvtpes5wmpjp.yaml
  100. 3 0
      contract_manager/store/contracts/CosmWasmContract/osmosis_osmo1hpdzqku55lmfmptpyj6wdlugqs5etr6teqf7r4yqjjrxjznjhtuqqu5kdh.yaml

+ 2 - 6
.github/workflows/ethereum-contract.yml

@@ -2,13 +2,13 @@ on:
   pull_request:
     paths:
       - target_chains/ethereum/contracts/**
-      - governance/xc_governance_sdk_js/**
+      - governance/xc_admin/packages/xc_admin_common/**
   push:
     branches:
       - main
     paths:
       - target_chains/ethereum/contracts/**
-      - governance/xc_governance_sdk_js/**
+      - governance/xc_admin/packages/xc_admin_common/**
 
 name: Ethereum Contract
 
@@ -22,10 +22,6 @@ jobs:
     steps:
       - uses: actions/checkout@v3
 
-      - name: Install XC-governance sdk dependencies
-        run: npm ci
-        working-directory: governance/xc_governance_sdk_js
-
       - name: Install contract npm dependencies
         run: npm ci
 

+ 5 - 0
.github/workflows/pre-commit.yml

@@ -16,4 +16,9 @@ jobs:
           profile: minimal
           toolchain: nightly-2023-03-01
           components: rustfmt, clippy
+      - uses: actions-rs/toolchain@v1
+        with:
+          profile: minimal
+          toolchain: nightly-2023-07-23
+          components: rustfmt, clippy
       - uses: pre-commit/action@v2.0.3

+ 1 - 1
.pre-commit-config.yaml

@@ -62,7 +62,7 @@ repos:
       - id: cargo-fmt-hermes
         name: Cargo format for Pyth Hermes
         language: "rust"
-        entry: cargo +nightly-2023-03-01 fmt --manifest-path ./hermes/Cargo.toml --all -- --config-path rustfmt.toml
+        entry: cargo +nightly-2023-07-23 fmt --manifest-path ./hermes/Cargo.toml --all -- --config-path rustfmt.toml
         pass_filenames: false
         files: hermes
       # Hooks for message buffer contract

+ 0 - 1
contract_manager/.gitignore

@@ -1,2 +1 @@
-node_modules/
 lib/

+ 2 - 1
contract_manager/package.json

@@ -15,10 +15,11 @@
     "url": "git+https://github.com/pyth-network/pyth-crosschain.git"
   },
   "dependencies": {
+    "@mysten/sui.js": "^0.37.1",
     "@certusone/wormhole-sdk": "^0.9.8",
     "@pythnetwork/cosmwasm-deploy-tools": "*",
     "@pythnetwork/price-service-client": "*",
-    "@pythnetwork/xc-governance-sdk": "*",
+    "@injectivelabs/networks": "1.0.68",
     "bs58": "^5.0.0",
     "ts-node": "^10.9.1",
     "typescript": "^4.9.3"

+ 102 - 0
contract_manager/scripts/check_proposal.ts

@@ -0,0 +1,102 @@
+import yargs from "yargs";
+import { hideBin } from "yargs/helpers";
+import { CosmWasmChain, EvmChain } from "../src/chains";
+import { createHash } from "crypto";
+import { DefaultStore } from "../src/store";
+import {
+  CosmosUpgradeContract,
+  EvmUpgradeContract,
+  getProposalInstructions,
+  MultisigParser,
+  WormholeMultisigInstruction,
+} from "xc_admin_common";
+import SquadsMesh from "@sqds/mesh";
+import {
+  getPythClusterApiUrl,
+  PythCluster,
+} from "@pythnetwork/client/lib/cluster";
+import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet";
+import { AccountMeta, Keypair, PublicKey } from "@solana/web3.js";
+import { EvmContract } from "../src/contracts/evm";
+
+const parser = yargs(hideBin(process.argv))
+  .scriptName("check_proposal.ts")
+  .usage("Usage: $0 --cluster <cluster_id> --proposal <proposal_address>")
+  .options({
+    cluster: {
+      type: "string",
+      demandOption: true,
+      desc: "Multsig Cluster name to check proposal on can be one of [devnet, testnet, mainnet-beta]",
+    },
+    proposal: {
+      type: "string",
+      demandOption: true,
+      desc: "The proposal address to check",
+    },
+  });
+
+async function main() {
+  const argv = await parser.argv;
+  const cluster = argv.cluster as PythCluster;
+  const squad = SquadsMesh.endpoint(
+    getPythClusterApiUrl(cluster),
+    new NodeWallet(Keypair.generate()) // dummy wallet
+  );
+  const transaction = await squad.getTransaction(new PublicKey(argv.proposal));
+  const instructions = await getProposalInstructions(squad, transaction);
+  const multisigParser = MultisigParser.fromCluster(cluster);
+  const parsedInstructions = instructions.map((instruction) => {
+    return multisigParser.parseInstruction({
+      programId: instruction.programId,
+      data: instruction.data as Buffer,
+      keys: instruction.keys as AccountMeta[],
+    });
+  });
+
+  for (const instruction of parsedInstructions) {
+    if (instruction instanceof WormholeMultisigInstruction) {
+      if (instruction.governanceAction instanceof EvmUpgradeContract) {
+        console.log(
+          `Verifying EVM Upgrade Contract on ${instruction.governanceAction.targetChainId}`
+        );
+        for (const chain of Object.values(DefaultStore.chains)) {
+          if (
+            chain instanceof EvmChain &&
+            chain.isMainnet() === (cluster === "mainnet-beta") &&
+            chain.wormholeChainName ===
+              instruction.governanceAction.targetChainId
+          ) {
+            const address = instruction.governanceAction.address;
+            const contract = new EvmContract(chain, address);
+            const code = await contract.getCodeDigestWithoutAddress();
+            // this should be the same keccak256 of the deployedCode property generated by truffle
+            console.log(`Address:${address} digest:${code}`);
+          }
+        }
+      }
+      if (instruction.governanceAction instanceof CosmosUpgradeContract) {
+        console.log(
+          `Verifying Cosmos Upgrade Contract on ${instruction.governanceAction.targetChainId}`
+        );
+        for (const chain of Object.values(DefaultStore.chains)) {
+          if (
+            chain instanceof CosmWasmChain &&
+            chain.wormholeChainName ===
+              instruction.governanceAction.targetChainId
+          ) {
+            const codeId = instruction.governanceAction.codeId;
+            const code = await chain.getCode(Number(codeId));
+            // this should be the same checksums.txt in our release file
+            console.log(
+              `Code Id:${codeId} digest:${createHash("sha256")
+                .update(code)
+                .digest("hex")}`
+            );
+          }
+        }
+      }
+    }
+  }
+}
+
+main();

+ 5 - 5
contract_manager/examples/deploy_cosmwasm.ts → contract_manager/scripts/deploy_cosmwasm.ts

@@ -1,13 +1,13 @@
 import yargs from "yargs";
 import { hideBin } from "yargs/helpers";
 import { CosmWasmChain } from "../src/chains";
-import { CosmWasmContract } from "../src/cosmwasm";
+import { CosmWasmContract } from "../src/contracts/cosmwasm";
 import { DefaultStore } from "../src/store";
 
 const parser = yargs(hideBin(process.argv))
   .scriptName("deploy_cosmwasm.ts")
   .usage(
-    "Usage: $0 --code <path/to/artifact.wasm> --mnemonic <mnemonic> --chain <chain>"
+    "Usage: $0 --code <path/to/artifact.wasm> --private-key <private-key> --chain <chain>"
   )
   .options({
     code: {
@@ -15,10 +15,10 @@ const parser = yargs(hideBin(process.argv))
       demandOption: true,
       desc: "Path to the artifact .wasm file",
     },
-    mnemonic: {
+    "private-key": {
       type: "string",
       demandOption: true,
-      desc: "Mnemonic to use for the deployment",
+      desc: "Private key to use for the deployment",
     },
     chain: {
       type: "string",
@@ -39,7 +39,7 @@ async function main() {
     await CosmWasmContract.deploy(
       DefaultStore.chains[argv.chain] as CosmWasmChain,
       wormholeContract,
-      argv.mnemonic,
+      argv["private-key"],
       code
     )
   );

+ 105 - 0
contract_manager/scripts/sync_governance_vaas.ts

@@ -0,0 +1,105 @@
+import yargs from "yargs";
+import { hideBin } from "yargs/helpers";
+import { DefaultStore } from "../src/store";
+import { PriceServiceConnection } from "@pythnetwork/price-service-client";
+import { SubmittedWormholeMessage, Vault } from "../src/governance";
+import { parseVaa } from "@certusone/wormhole-sdk";
+import { decodeGovernancePayload } from "xc_admin_common";
+
+const parser = yargs(hideBin(process.argv))
+  .usage(
+    "Tries to execute all vaas on a contract.\n" +
+      "Useful for recently deployed contracts.\n" +
+      "Usage: $0 --contract <contract_id> --private-key <private-key>"
+  )
+  .options({
+    contract: {
+      type: "string",
+      demandOption: true,
+      desc: "Contract to execute governance vaas for",
+    },
+    "private-key": {
+      type: "string",
+      demandOption: true,
+      desc: "Private key to sign the transactions executing the governance VAAs. Hex format, without 0x prefix.",
+    },
+    offset: {
+      type: "number",
+      desc: "Starting sequence number to use, if not provided will start from contract last executed governance sequence number",
+    },
+  });
+
+async function main() {
+  const argv = await parser.argv;
+  const contract = DefaultStore.contracts[argv.contract];
+  if (!contract) {
+    throw new Error(`Contract ${argv.contract} not found`);
+  }
+  const governanceSource = await contract.getGovernanceDataSource();
+  const mainnetVault =
+    DefaultStore.vaults[
+      "mainnet-beta_FVQyHcooAtThJ83XFrNnv74BcinbRH3bRmfFamAHBfuj"
+    ];
+  const devnetVault =
+    DefaultStore.vaults["devnet_6baWtW1zTUVMSJHJQVxDUXWzqrQeYBr6mu31j3bTKwY3"];
+  let matchedVault: Vault;
+  if (
+    (await devnetVault.getEmitter()).toBuffer().toString("hex") ===
+    governanceSource.emitterAddress
+  ) {
+    console.log("devnet multisig matches governance source");
+    matchedVault = devnetVault;
+  } else if (
+    (await mainnetVault.getEmitter()).toBuffer().toString("hex") ===
+    governanceSource.emitterAddress
+  ) {
+    console.log("mainnet multisig matches governance source");
+    matchedVault = mainnetVault;
+  } else {
+    throw new Error(
+      "can not find a multisig that matches the governance source of the contract"
+    );
+  }
+  let lastExecuted = await contract.getLastExecutedGovernanceSequence();
+  console.log("last executed governance sequence", lastExecuted);
+  if (argv.offset && argv.offset > lastExecuted) {
+    console.log("skipping to offset", argv.offset);
+    lastExecuted = argv.offset - 1;
+  }
+  console.log("Starting from sequence number", lastExecuted);
+  while (true) {
+    const submittedWormholeMessage = new SubmittedWormholeMessage(
+      await matchedVault.getEmitter(),
+      lastExecuted + 1,
+      matchedVault.cluster
+    );
+    let vaa: Buffer;
+    try {
+      vaa = await submittedWormholeMessage.fetchVaa();
+    } catch (e) {
+      console.log(e);
+      console.log("no vaa found for sequence", lastExecuted + 1);
+      break;
+    }
+    const parsedVaa = parseVaa(vaa);
+    const action = decodeGovernancePayload(parsedVaa.payload);
+    if (!action) {
+      console.log("can not decode vaa, skipping");
+    } else if (
+      action.targetChainId === "unset" ||
+      contract.getChain().wormholeChainName === action.targetChainId
+    ) {
+      console.log("executing vaa", lastExecuted + 1);
+      await contract.executeGovernanceInstruction(argv["private-key"], vaa);
+    } else {
+      console.log(
+        `vaa is not for this chain (${
+          contract.getChain().wormholeChainName
+        } != ${action.targetChainId}, skipping`
+      );
+    }
+    lastExecuted++;
+  }
+}
+
+main();

+ 41 - 0
contract_manager/scripts/upload_cosmwasm.ts

@@ -0,0 +1,41 @@
+import yargs from "yargs";
+import { hideBin } from "yargs/helpers";
+import { CosmWasmChain } from "../src/chains";
+import { CosmWasmContract } from "../src/contracts/cosmwasm";
+import { DefaultStore } from "../src/store";
+
+const parser = yargs(hideBin(process.argv))
+  .scriptName("upload_cosmwasm.ts")
+  .usage(
+    "Usage: $0 --code <path/to/artifact.wasm> --private-key <private-key> --chain <chain>"
+  )
+  .options({
+    code: {
+      type: "string",
+      demandOption: true,
+      desc: "Path to the artifact .wasm file",
+    },
+    "private-key": {
+      type: "string",
+      demandOption: true,
+      desc: "Private key to use for the deployment",
+    },
+    chain: {
+      type: "string",
+      demandOption: true,
+      desc: "Chain to upload the code on. Can be one of the chains available in the store",
+    },
+  });
+
+async function main() {
+  const argv = await parser.argv;
+  const { code } = argv;
+  const { codeId } = await CosmWasmContract.storeCode(
+    DefaultStore.chains[argv.chain] as CosmWasmChain,
+    argv["private-key"],
+    code
+  );
+  console.log(`Successfully uploaded code with id ${codeId}`);
+}
+
+main();

+ 0 - 103
contract_manager/src/aptos.ts

@@ -1,103 +0,0 @@
-import { Contract } from "./base";
-import { AptosChain, Chain } from "./chains";
-import { DataSource, HexString32Bytes } from "@pythnetwork/xc-governance-sdk";
-import { AptosClient } from "aptos";
-
-export class AptosContract extends Contract {
-  static type: string = "AptosContract";
-
-  /**
-   * Given the ids of the pyth state and wormhole state, create a new AptosContract
-   * 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: AptosChain,
-    public stateId: string,
-    public wormholeStateId: string
-  ) {
-    super();
-  }
-
-  static fromJson(chain: Chain, parsed: any): AptosContract {
-    if (parsed.type !== AptosContract.type) throw new Error("Invalid type");
-    if (!(chain instanceof AptosChain))
-      throw new Error(`Wrong chain type ${chain}`);
-    return new AptosContract(chain, parsed.stateId, parsed.wormholeStateId);
-  }
-
-  executeGovernanceInstruction(sender: any, vaa: Buffer): Promise<any> {
-    throw new Error("Method not implemented.");
-  }
-
-  getStateResources() {
-    const client = this.chain.getClient();
-    return client.getAccountResources(this.stateId);
-  }
-
-  /**
-   * Returns the first occurrence of a resource with the given type in the pyth package state
-   * @param type
-   */
-  async findResource(type: string) {
-    const resources = await this.getStateResources();
-    for (const resource of resources) {
-      if (resource.type === `${this.stateId}::state::${type}`) {
-        return resource.data;
-      }
-    }
-    throw new Error(`${type} resource not found in state ${this.stateId}`);
-  }
-
-  async getBaseUpdateFee() {
-    const data = (await this.findResource("BaseUpdateFee")) as any;
-    return { amount: data.fee };
-  }
-
-  getChain(): AptosChain {
-    return this.chain;
-  }
-
-  async getDataSources(): Promise<DataSource[]> {
-    const data = (await this.findResource("DataSources")) as any;
-    return data.sources.keys.map((source: any) => {
-      return new DataSource(
-        Number(source.emitter_chain),
-        new HexString32Bytes(source.emitter_address.external_address)
-      );
-    });
-  }
-
-  async getGovernanceDataSource(): Promise<DataSource> {
-    const data = (await this.findResource("GovernanceDataSource")) as any;
-    return new DataSource(
-      Number(data.source.emitter_chain),
-      new HexString32Bytes(data.source.emitter_address.external_address)
-    );
-  }
-
-  getId(): string {
-    return `${this.chain.getId()}_${this.stateId}`;
-  }
-
-  getType(): string {
-    return AptosContract.type;
-  }
-
-  async getValidTimePeriod() {
-    const data = (await this.findResource("StalePriceThreshold")) as any;
-    return Number(data.threshold_secs);
-  }
-
-  toJson() {
-    return {
-      chain: this.chain.id,
-      stateId: this.stateId,
-      wormholeStateId: this.wormholeStateId,
-      type: AptosContract.type,
-    };
-  }
-}

+ 33 - 8
contract_manager/src/base.ts

@@ -1,9 +1,4 @@
-import {
-  CHAINS,
-  DataSource,
-  HexString32Bytes,
-  SetFeeInstruction,
-} from "@pythnetwork/xc-governance-sdk";
+import { DataSource } from "xc_admin_common";
 import { Chain } from "./chains";
 
 export abstract class Storable {
@@ -25,6 +20,18 @@ export abstract class Storable {
   abstract toJson(): any;
 }
 
+export interface Price {
+  price: string;
+  conf: string;
+  publishTime: string;
+  expo: string;
+}
+
+export interface PriceFeed {
+  price: Price;
+  emaPrice: Price;
+}
+
 export abstract class Contract extends Storable {
   /**
    * Returns the time period in seconds that stale data is considered valid for.
@@ -47,12 +54,30 @@ export abstract class Contract extends Storable {
    */
   abstract getBaseUpdateFee(): Promise<{ amount: string; denom?: string }>;
 
+  /**
+   * Returns the last governance sequence that was executed on this contract
+   * this number increases based on the sequence number of the governance messages
+   * that are executed on this contract
+   *
+   * This is used to determine which governance messages are stale and can not be executed
+   */
+  abstract getLastExecutedGovernanceSequence(): Promise<number>;
+
+  /**
+   * Returns the price feed for the given feed id or undefined if not found
+   * @param feedId hex encoded feed id without 0x prefix
+   */
+  abstract getPriceFeed(feedId: string): Promise<PriceFeed | undefined>;
+
   /**
    * Executes the governance instruction contained in the VAA using the sender credentials
-   * @param sender based on the contract type, this can be a private key, a mnemonic, a wallet, etc.
+   * @param senderPrivateKey private key of the sender in hex format without 0x prefix
    * @param vaa the VAA to execute
    */
-  abstract executeGovernanceInstruction(sender: any, vaa: Buffer): Promise<any>;
+  abstract executeGovernanceInstruction(
+    senderPrivateKey: string,
+    vaa: Buffer
+  ): Promise<any>;
 
   /**
    * Returns the single data source that this contract accepts governance messages from

+ 187 - 81
contract_manager/src/chains.ts

@@ -1,37 +1,83 @@
-import { readdirSync, readFileSync, writeFileSync } from "fs";
 import { Storable } from "./base";
 import {
+  ChainName,
   CHAINS,
-  CosmwasmUpgradeContractInstruction,
-  EthereumUpgradeContractInstruction,
-  HexString20Bytes,
-  HexString32Bytes,
-  SetFeeInstruction,
-  SuiAuthorizeUpgradeContractInstruction,
-} from "@pythnetwork/xc-governance-sdk";
-import { BufferBuilder } from "@pythnetwork/xc-governance-sdk/lib/serialize";
+  SetFee,
+  CosmosUpgradeContract,
+  EvmUpgradeContract,
+  SuiAuthorizeUpgradeContract,
+  AptosAuthorizeUpgradeContract,
+  toChainId,
+  SetDataSources,
+  SetValidPeriod,
+  DataSource,
+} from "xc_admin_common";
 import { AptosClient } from "aptos";
+import Web3 from "web3";
+import { CosmwasmQuerier } from "@pythnetwork/cosmwasm-deploy-tools";
 
 export abstract class Chain extends Storable {
-  protected constructor(public id: string) {
+  public wormholeChainName: ChainName;
+
+  /**
+   * Creates a new Chain object
+   * @param id unique id representing this chain
+   * @param mainnet whether this chain is mainnet or testnet/devnet
+   * @param wormholeChainName the name of the wormhole chain that this chain is associated with.
+   * Note that pyth has included additional chain names and ids to the wormhole spec.
+   * @protected
+   */
+  protected constructor(
+    protected id: string,
+    protected mainnet: boolean,
+    wormholeChainName: string
+  ) {
     super();
+    this.wormholeChainName = wormholeChainName as ChainName;
+    if (toChainId(this.wormholeChainName) === undefined)
+      throw new Error(`Invalid chain name ${wormholeChainName}`);
   }
 
   getId(): string {
     return this.id;
   }
 
+  isMainnet(): boolean {
+    return this.mainnet;
+  }
+
   /**
    * Returns the payload for a governance SetFee instruction for contracts deployed on this chain
    * @param fee the new fee to set
    * @param exponent the new fee exponent to set
    */
   generateGovernanceSetFeePayload(fee: number, exponent: number): Buffer {
-    return new SetFeeInstruction(
-      CHAINS[this.getId() as keyof typeof CHAINS],
+    return new SetFee(
+      this.wormholeChainName,
       BigInt(fee),
       BigInt(exponent)
-    ).serialize();
+    ).encode();
+  }
+
+  /**
+   * Returns the payload for a governance SetDataSources instruction for contracts deployed on this chain
+   * @param datasources the new datasources
+   */
+  generateGovernanceSetDataSources(datasources: DataSource[]): Buffer {
+    return new SetDataSources(this.wormholeChainName, datasources).encode();
+  }
+
+  /**
+   * Returns the payload for a governance SetStalePriceThreshold instruction for contracts deployed on this chain
+   * @param newValidStalePriceThreshold the new stale price threshold in seconds
+   */
+  generateGovernanceSetStalePriceThreshold(
+    newValidStalePriceThreshold: bigint
+  ): Buffer {
+    return new SetValidPeriod(
+      this.wormholeChainName,
+      newValidStalePriceThreshold
+    ).encode();
   }
 
   /**
@@ -41,24 +87,53 @@ export abstract class Chain extends Storable {
   abstract generateGovernanceUpgradePayload(upgradeInfo: any): Buffer;
 }
 
+export class GlobalChain extends Chain {
+  static type: string = "GlobalChain";
+  constructor() {
+    super("global", true, "unset");
+  }
+  generateGovernanceUpgradePayload(upgradeInfo: any): Buffer {
+    throw new Error(
+      "Can not create a governance message for upgrading contracts on all chains!"
+    );
+  }
+
+  getType(): string {
+    return GlobalChain.type;
+  }
+
+  toJson(): any {
+    return {
+      id: this.id,
+      wormholeChainName: this.wormholeChainName,
+      mainnet: this.mainnet,
+      type: GlobalChain.type,
+    };
+  }
+}
+
 export class CosmWasmChain extends Chain {
   static type: string = "CosmWasmChain";
 
   constructor(
     id: string,
+    mainnet: boolean,
+    wormholeChainName: string,
     public querierEndpoint: string,
     public executorEndpoint: string,
     public gasPrice: string,
     public prefix: string,
     public feeDenom: string
   ) {
-    super(id);
+    super(id, mainnet, wormholeChainName);
   }
 
   static fromJson(parsed: any): CosmWasmChain {
     if (parsed.type !== CosmWasmChain.type) throw new Error("Invalid type");
     return new CosmWasmChain(
       parsed.id,
+      parsed.mainnet,
+      parsed.wormholeChainName,
       parsed.querierEndpoint,
       parsed.executorEndpoint,
       parsed.gasPrice,
@@ -72,6 +147,8 @@ export class CosmWasmChain extends Chain {
       querierEndpoint: this.querierEndpoint,
       executorEndpoint: this.executorEndpoint,
       id: this.id,
+      wormholeChainName: this.wormholeChainName,
+      mainnet: this.mainnet,
       gasPrice: this.gasPrice,
       prefix: this.prefix,
       feeDenom: this.feeDenom,
@@ -83,29 +160,43 @@ export class CosmWasmChain extends Chain {
     return CosmWasmChain.type;
   }
 
+  async getCode(codeId: number): Promise<Buffer> {
+    const chainQuerier = await CosmwasmQuerier.connect(this.querierEndpoint);
+    return await chainQuerier.getCode({ codeId });
+  }
+
   generateGovernanceUpgradePayload(codeId: bigint): Buffer {
-    return new CosmwasmUpgradeContractInstruction(
-      CHAINS[this.getId() as keyof typeof CHAINS],
-      codeId
-    ).serialize();
+    return new CosmosUpgradeContract(this.wormholeChainName, codeId).encode();
   }
 }
 
 export class SuiChain extends Chain {
   static type: string = "SuiChain";
 
-  constructor(id: string, public rpcUrl: string) {
-    super(id);
+  constructor(
+    id: string,
+    mainnet: boolean,
+    wormholeChainName: string,
+    public rpcUrl: string
+  ) {
+    super(id, mainnet, wormholeChainName);
   }
 
   static fromJson(parsed: any): SuiChain {
     if (parsed.type !== SuiChain.type) throw new Error("Invalid type");
-    return new SuiChain(parsed.id, parsed.rpcUrl);
+    return new SuiChain(
+      parsed.id,
+      parsed.mainnet,
+      parsed.wormholeChainName,
+      parsed.rpcUrl
+    );
   }
 
   toJson(): any {
     return {
       id: this.id,
+      wormholeChainName: this.wormholeChainName,
+      mainnet: this.mainnet,
       rpcUrl: this.rpcUrl,
       type: SuiChain.type,
     };
@@ -115,97 +206,105 @@ export class SuiChain extends Chain {
     return SuiChain.type;
   }
 
-  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();
-  }
-
+  /**
+   * Returns the payload for a governance contract upgrade instruction for contracts deployed on this chain
+   * @param digest hex string of the 32 byte digest for the new package without the 0x prefix
+   */
   generateGovernanceUpgradePayload(digest: string): Buffer {
-    let setFee = new SuiAuthorizeUpgradeContractInstruction(
-      CHAINS["sui"],
-      new HexString32Bytes(digest)
-    ).serialize();
-    return this.wrapWithWormholeGovernancePayload(0, setFee);
-  }
-
-  generateGovernanceSetFeePayload(fee: number, exponent: number): Buffer {
-    let setFee = new SetFeeInstruction(
-      CHAINS["sui"],
-      BigInt(fee),
-      BigInt(exponent)
-    ).serialize();
-    return this.wrapWithWormholeGovernancePayload(3, setFee);
+    return new SuiAuthorizeUpgradeContract(
+      this.wormholeChainName,
+      digest
+    ).encode();
   }
 }
 
-export class EVMChain extends Chain {
-  static type: string = "EVMChain";
+export class EvmChain extends Chain {
+  static type: string = "EvmChain";
 
-  constructor(id: string, public rpcUrl: string) {
-    super(id);
+  constructor(
+    id: string,
+    mainnet: boolean,
+    wormholeChainName: string,
+    private rpcUrl: string,
+    private networkId: number
+  ) {
+    super(id, mainnet, wormholeChainName);
+  }
+
+  static fromJson(parsed: any): EvmChain {
+    if (parsed.type !== EvmChain.type) throw new Error("Invalid type");
+    return new EvmChain(
+      parsed.id,
+      parsed.mainnet,
+      parsed.wormholeChainName,
+      parsed.rpcUrl,
+      parsed.networkId
+    );
   }
 
-  static fromJson(parsed: any): EVMChain {
-    if (parsed.type !== EVMChain.type) throw new Error("Invalid type");
-    return new EVMChain(parsed.id, parsed.rpcUrl);
+  getRpcUrl(): string {
+    return this.rpcUrl;
   }
 
-  generateGovernanceUpgradePayload(address: HexString20Bytes): Buffer {
-    return new EthereumUpgradeContractInstruction(
-      CHAINS[this.getId() as keyof typeof CHAINS],
-      address
-    ).serialize();
+  /**
+   * Returns the payload for a governance contract upgrade instruction for contracts deployed on this chain
+   * @param address hex string of the 20 byte address of the contract to upgrade to without the 0x prefix
+   */
+  generateGovernanceUpgradePayload(address: string): Buffer {
+    return new EvmUpgradeContract(this.wormholeChainName, address).encode();
   }
 
   toJson(): any {
     return {
       id: this.id,
+      wormholeChainName: this.wormholeChainName,
+      mainnet: this.mainnet,
       rpcUrl: this.rpcUrl,
-      type: EVMChain.type,
+      networkId: this.networkId,
+      type: EvmChain.type,
     };
   }
 
   getType(): string {
-    return EVMChain.type;
+    return EvmChain.type;
+  }
+
+  async getGasPrice() {
+    const web3 = new Web3(this.getRpcUrl());
+    let gasPrice = await web3.eth.getGasPrice();
+    // some testnets have inaccuarte gas prices that leads to transactions not being mined, we double it since it's free!
+    if (!this.isMainnet()) {
+      gasPrice = (BigInt(gasPrice) * 2n).toString();
+    }
+    return gasPrice;
   }
 }
 
 export class AptosChain extends Chain {
   static type = "AptosChain";
 
-  constructor(id: string, public rpcUrl: string) {
-    super(id);
+  constructor(
+    id: string,
+    mainnet: boolean,
+    wormholeChainName: string,
+    public rpcUrl: string
+  ) {
+    super(id, mainnet, wormholeChainName);
   }
 
   getClient(): AptosClient {
     return new AptosClient(this.rpcUrl);
   }
 
+  /**
+   * Returns the payload for a governance contract upgrade instruction for contracts deployed on this chain
+   * @param digest hex string of the 32 byte digest for the new package without the 0x prefix
+   */
   generateGovernanceUpgradePayload(digest: string): Buffer {
-    return new SuiAuthorizeUpgradeContractInstruction(
-      CHAINS["aptos"],
-      new HexString32Bytes(digest)
-    ).serialize();
-  }
-
-  generateGovernanceSetFeePayload(fee: number, exponent: number): Buffer {
-    return new SetFeeInstruction(
-      CHAINS["aptos"], // should always be aptos (22) no matter devnet or testnet or mainnet
-      BigInt(fee),
-      BigInt(exponent)
-    ).serialize();
+    return new AptosAuthorizeUpgradeContract(
+      this.wormholeChainName,
+      digest
+    ).encode();
   }
 
   getType(): string {
@@ -215,6 +314,8 @@ export class AptosChain extends Chain {
   toJson(): any {
     return {
       id: this.id,
+      wormholeChainName: this.wormholeChainName,
+      mainnet: this.mainnet,
       rpcUrl: this.rpcUrl,
       type: AptosChain.type,
     };
@@ -222,6 +323,11 @@ export class AptosChain extends Chain {
 
   static fromJson(parsed: any): AptosChain {
     if (parsed.type !== AptosChain.type) throw new Error("Invalid type");
-    return new AptosChain(parsed.id, parsed.rpcUrl);
+    return new AptosChain(
+      parsed.id,
+      parsed.mainnet,
+      parsed.wormholeChainName,
+      parsed.rpcUrl
+    );
   }
 }

+ 190 - 0
contract_manager/src/contracts/aptos.ts

@@ -0,0 +1,190 @@
+import { Contract, PriceFeed } from "../base";
+import { AptosAccount, BCS, TxnBuilderTypes } from "aptos";
+import { AptosChain, Chain } from "../chains";
+import { DataSource } from "xc_admin_common";
+
+export class AptosContract extends Contract {
+  static type: string = "AptosContract";
+
+  /**
+   * Given the ids of the pyth state and wormhole state, create a new AptosContract
+   * 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: AptosChain,
+    public stateId: string,
+    public wormholeStateId: string
+  ) {
+    super();
+  }
+
+  static fromJson(chain: Chain, parsed: any): AptosContract {
+    if (parsed.type !== AptosContract.type) throw new Error("Invalid type");
+    if (!(chain instanceof AptosChain))
+      throw new Error(`Wrong chain type ${chain}`);
+    return new AptosContract(chain, parsed.stateId, parsed.wormholeStateId);
+  }
+
+  async executeGovernanceInstruction(
+    senderPrivateKey: string,
+    vaa: Buffer
+  ): Promise<any> {
+    const txPayload = new TxnBuilderTypes.TransactionPayloadEntryFunction(
+      TxnBuilderTypes.EntryFunction.natural(
+        `${this.stateId}::governance`,
+        "execute_governance_instruction",
+        [],
+        [BCS.bcsSerializeBytes(vaa)]
+      )
+    );
+    return this.sendTransaction(senderPrivateKey, txPayload);
+  }
+
+  private sendTransaction(
+    senderPrivateKey: string,
+    txPayload: TxnBuilderTypes.TransactionPayloadEntryFunction
+  ) {
+    const client = this.chain.getClient();
+    const sender = new AptosAccount(
+      new Uint8Array(Buffer.from(senderPrivateKey, "hex"))
+    );
+    return client.generateSignSubmitWaitForTransaction(sender, txPayload, {
+      maxGasAmount: BigInt(30000),
+    });
+  }
+
+  async executeUpdatePriceFeed(
+    senderPrivateKey: string,
+    vaas: Buffer[]
+  ): Promise<any> {
+    const txPayload = new TxnBuilderTypes.TransactionPayloadEntryFunction(
+      TxnBuilderTypes.EntryFunction.natural(
+        `${this.stateId}::pyth`,
+        "update_price_feeds_with_funder",
+        [],
+        [BCS.serializeVectorWithFunc(vaas, "serializeBytes")]
+      )
+    );
+    return this.sendTransaction(senderPrivateKey, txPayload);
+  }
+
+  getStateResources() {
+    const client = this.chain.getClient();
+    return client.getAccountResources(this.stateId);
+  }
+
+  /**
+   * Returns the first occurrence of a resource with the given type in the pyth package state
+   * @param type
+   */
+  async findResource(type: string) {
+    const resources = await this.getStateResources();
+    for (const resource of resources) {
+      if (resource.type === `${this.stateId}::state::${type}`) {
+        return resource.data;
+      }
+    }
+    throw new Error(`${type} resource not found in state ${this.stateId}`);
+  }
+
+  async getBaseUpdateFee() {
+    const data = (await this.findResource("BaseUpdateFee")) as any;
+    return { amount: data.fee };
+  }
+
+  getChain(): AptosChain {
+    return this.chain;
+  }
+
+  private parsePrice(priceInfo: any) {
+    let expo = priceInfo.expo.magnitude;
+    if (priceInfo.expo.negative) expo = "-" + expo;
+    let price = priceInfo.price.magnitude;
+    if (priceInfo.price.negative) price = "-" + price;
+    return {
+      conf: priceInfo.conf,
+      publishTime: priceInfo.timestamp,
+      expo,
+      price,
+    };
+  }
+
+  async getPriceFeed(feedId: string): Promise<PriceFeed | undefined> {
+    const client = this.chain.getClient();
+    const res = (await this.findResource("LatestPriceInfo")) as any;
+    const handle = res.info.handle;
+    try {
+      const priceItemRes = await client.getTableItem(handle, {
+        key_type: `${this.stateId}::price_identifier::PriceIdentifier`,
+        value_type: `${this.stateId}::price_info::PriceInfo`,
+        key: {
+          bytes: feedId,
+        },
+      });
+      return {
+        price: this.parsePrice(priceItemRes.price_feed.price),
+        emaPrice: this.parsePrice(priceItemRes.price_feed.ema_price),
+      };
+    } catch (e: any) {
+      if (e.errorCode === "table_item_not_found") return undefined;
+      throw e;
+    }
+  }
+
+  async getDataSources(): Promise<DataSource[]> {
+    const data = (await this.findResource("DataSources")) as any;
+    return data.sources.keys.map((source: any) => {
+      return {
+        emitterChain: Number(source.emitter_chain),
+        emitterAddress: source.emitter_address.external_address.replace(
+          "0x",
+          ""
+        ),
+      };
+    });
+  }
+
+  async getGovernanceDataSource(): Promise<DataSource> {
+    const data = (await this.findResource("GovernanceDataSource")) as any;
+    return {
+      emitterChain: Number(data.source.emitter_chain),
+      emitterAddress: data.source.emitter_address.external_address.replace(
+        "0x",
+        ""
+      ),
+    };
+  }
+
+  async getLastExecutedGovernanceSequence() {
+    const data = (await this.findResource(
+      "LastExecutedGovernanceSequence"
+    )) as any;
+    return Number(data.sequence);
+  }
+
+  getId(): string {
+    return `${this.chain.getId()}_${this.stateId}`;
+  }
+
+  getType(): string {
+    return AptosContract.type;
+  }
+
+  async getValidTimePeriod() {
+    const data = (await this.findResource("StalePriceThreshold")) as any;
+    return Number(data.threshold_secs);
+  }
+
+  toJson() {
+    return {
+      chain: this.chain.getId(),
+      stateId: this.stateId,
+      wormholeStateId: this.wormholeStateId,
+      type: AptosContract.type,
+    };
+  }
+}

+ 76 - 65
contract_manager/src/cosmwasm.ts → contract_manager/src/contracts/cosmwasm.ts

@@ -1,24 +1,20 @@
-import { Chain, CosmWasmChain } from "./chains";
+import { Chain, CosmWasmChain } from "../chains";
 import { readFileSync } from "fs";
-import { getPythConfig } from "@pythnetwork/cosmwasm-deploy-tools/lib/configs";
-import {
-  CHAINS,
-  DataSource,
-  HexString32Bytes,
-} from "@pythnetwork/xc-governance-sdk";
-import { DeploymentType } from "@pythnetwork/cosmwasm-deploy-tools/lib/helper";
 import {
+  ContractInfoResponse,
   CosmwasmExecutor,
+  CosmwasmQuerier,
+  DeploymentType,
+  getPythConfig,
+  InjectiveExecutor,
+  Price,
   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 { CHAINS, DataSource } from "xc_admin_common";
+import { Network } from "@injectivelabs/networks";
 import { CosmWasmClient } from "@cosmjs/cosmwasm-stargate";
-import { Contract } from "./base";
+import { Contract } from "../base";
 
 /**
  * Variables here need to be snake case to match the on-chain contract configs
@@ -45,10 +41,10 @@ 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"))
-      );
+      return {
+        emitterChain: Number(chain_id),
+        emitterAddress: Buffer.from(emitter, "base64").toString("hex"),
+      };
     });
   }
 
@@ -56,12 +52,10 @@ export class CosmWasmContract extends Contract {
     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")
-      )
-    );
+    return {
+      emitterChain: Number(chainId),
+      emitterAddress: Buffer.from(emitterAddress, "base64").toString("hex"),
+    };
   }
 
   static type = "CosmWasmContract";
@@ -96,19 +90,19 @@ export class CosmWasmContract extends Contract {
   }
 
   /**
-   * Stores the wasm code on the specified chain using the provided mnemonic as the signer
+   * Stores the wasm code on the specified chain using the provided private key as the signer
    * You can find the wasm artifacts from the repo releases
    * @param chain chain to store the code on
-   * @param mnemonic mnemonic to use for signing the transaction
+   * @param privateKey private key to use for signing the transaction in hex format without 0x prefix
    * @param wasmPath path in your local filesystem to the wasm artifact
    */
   static async storeCode(
     chain: CosmWasmChain,
-    mnemonic: string,
+    privateKey: string,
     wasmPath: string
   ) {
     const contractBytes = readFileSync(wasmPath);
-    let executor = this.getExecutor(chain, mnemonic);
+    let executor = await this.getExecutor(chain, privateKey);
     return executor.storeCode({ contractBytes });
   }
 
@@ -117,15 +111,15 @@ export class CosmWasmContract extends Contract {
    * @param chain chain to deploy to
    * @param codeId codeId of the uploaded wasm code. You can get this from the storeCode result
    * @param config deployment config for initializing the contract (data sources, governance source, etc)
-   * @param mnemonic mnemonic to use for signing the transaction
+   * @param privateKey private key to use for signing the transaction in hex format without 0x prefix
    */
   static async initialize(
     chain: CosmWasmChain,
     codeId: number,
     config: CosmWasmContract.DeploymentConfig,
-    mnemonic: string
+    privateKey: string
   ): Promise<CosmWasmContract> {
-    let executor = this.getExecutor(chain, mnemonic);
+    let executor = await this.getExecutor(chain, privateKey);
     let result = await executor.instantiateContract({
       codeId: codeId,
       instMsg: config,
@@ -146,26 +140,30 @@ export class CosmWasmContract extends Contract {
    * more control over the deployment process.
    * @param chain
    * @param wormholeContract
-   * @param mnemonic
+   * @param privateKey private key to use for signing the transaction in hex format without 0x prefix
    * @param wasmPath
    */
   static async deploy(
     chain: CosmWasmChain,
     wormholeContract: string,
-    mnemonic: string,
+    privateKey: string,
     wasmPath: string
   ): Promise<CosmWasmContract> {
     let config = this.getDeploymentConfig(chain, "edge", wormholeContract);
-    const { codeId } = await this.storeCode(chain, mnemonic, wasmPath);
-    return this.initialize(chain, codeId, config, mnemonic);
+    const { codeId } = await this.storeCode(chain, privateKey, wasmPath);
+    return this.initialize(chain, codeId, config, privateKey);
   }
 
-  private static getExecutor(chain: CosmWasmChain, mnemonic: string) {
-    // TODO: logic for injective
+  private static async getExecutor(chain: CosmWasmChain, privateKey: string) {
+    if (chain.getId().indexOf("injective") > -1) {
+      return InjectiveExecutor.fromPrivateKey(
+        chain.isMainnet() ? Network.Mainnet : Network.Testnet,
+        privateKey
+      );
+    }
     return new CosmwasmExecutor(
       chain.executorEndpoint,
-      mnemonic,
-      chain.prefix,
+      await CosmwasmExecutor.getSignerFromPrivateKey(privateKey, chain.prefix),
       chain.gasPrice + chain.feeDenom
     );
   }
@@ -176,7 +174,7 @@ export class CosmWasmContract extends Contract {
 
   toJson() {
     return {
-      chain: this.chain.id,
+      chain: this.chain.getId(),
       address: this.address,
       type: CosmWasmContract.type,
     };
@@ -216,12 +214,34 @@ export class CosmWasmContract extends Contract {
     return config;
   }
 
+  async getLastExecutedGovernanceSequence() {
+    const config = await this.getConfig();
+    return Number(config.config_v1.governance_sequence_number);
+  }
+
   // TODO: function for upgrading the contract
   // TODO: Cleanup and more strict linter to convert let to const
 
-  async getPriceFeed(feedId: string): Promise<any> {
+  private parsePrice(priceInfo: Price) {
+    return {
+      conf: priceInfo.conf.toString(),
+      publishTime: priceInfo.publish_time.toString(),
+      expo: priceInfo.expo.toString(),
+      price: priceInfo.price.toString(),
+    };
+  }
+
+  async getPriceFeed(feedId: string) {
     let querier = await this.getQuerier();
-    return querier.getPriceFeed(this.address, feedId);
+    try {
+      const response = await querier.getPriceFeed(this.address, feedId);
+      return {
+        price: this.parsePrice(response.price),
+        emaPrice: this.parsePrice(response.ema_price),
+      };
+    } catch (e) {
+      return undefined;
+    }
   }
 
   equalDataSources(
@@ -277,37 +297,23 @@ export class CosmWasmContract extends Contract {
     else return "unknown";
   }
 
-  async executeUpdatePriceFeed(feedId: string, mnemonic: string) {
-    const deploymentType = await this.getDeploymentType();
-    const priceServiceConnection = new PriceServiceConnection(
-      deploymentType === "stable"
-        ? "https://xc-mainnet.pyth.network"
-        : "https://xc-testnet.pyth.network"
-    );
-
-    const vaas = await priceServiceConnection.getLatestVaas([feedId]);
-    const fund = await this.getUpdateFee(vaas);
-    let executor = new CosmwasmExecutor(
-      this.chain.executorEndpoint,
-      mnemonic,
-      this.chain.prefix,
-      this.chain.gasPrice + this.chain.feeDenom
+  async executeUpdatePriceFeed(senderPrivateKey: string, vaas: Buffer[]) {
+    const base64Vaas = vaas.map((v) => v.toString("base64"));
+    const fund = await this.getUpdateFee(base64Vaas);
+    let executor = await CosmWasmContract.getExecutor(
+      this.chain,
+      senderPrivateKey
     );
     let pythExecutor = new PythWrapperExecutor(executor);
     return pythExecutor.executeUpdatePriceFeeds({
       contractAddr: this.address,
-      vaas,
+      vaas: base64Vaas,
       fund,
     });
   }
 
-  async executeGovernanceInstruction(mnemonic: string, vaa: Buffer) {
-    let executor = new CosmwasmExecutor(
-      this.chain.executorEndpoint,
-      mnemonic,
-      this.chain.prefix,
-      this.chain.gasPrice + this.chain.feeDenom
-    );
+  async executeGovernanceInstruction(privateKey: string, vaa: Buffer) {
+    let executor = await CosmWasmContract.getExecutor(this.chain, privateKey);
     let pythExecutor = new PythWrapperExecutor(executor);
     return pythExecutor.executeGovernanceInstruction({
       contractAddr: this.address,
@@ -325,6 +331,11 @@ export class CosmWasmContract extends Contract {
     return config.config_v1.fee;
   }
 
+  async getVersion(): Promise<any> {
+    const config = await this.getConfig();
+    return config.contract_version;
+  }
+
   getChain(): CosmWasmChain {
     return this.chain;
   }

+ 438 - 0
contract_manager/src/contracts/evm.ts

@@ -0,0 +1,438 @@
+import Web3 from "web3"; //TODO: decide on using web3 or ethers.js
+import PythInterfaceAbi from "@pythnetwork/pyth-sdk-solidity/abis/IPyth.json";
+import { Contract } from "../base";
+import { Chain, EvmChain } from "../chains";
+import { DataSource } from "xc_admin_common";
+
+// Just to make sure tx gas limit is enough
+const GAS_ESTIMATE_MULTIPLIER = 2;
+const EXTENDED_PYTH_ABI = [
+  {
+    inputs: [],
+    name: "wormhole",
+    outputs: [
+      {
+        internalType: "contract IWormhole",
+        name: "",
+        type: "address",
+      },
+    ],
+    stateMutability: "view",
+    type: "function",
+  },
+  {
+    inputs: [],
+    name: "governanceDataSource",
+    outputs: [
+      {
+        components: [
+          {
+            internalType: "uint16",
+            name: "chainId",
+            type: "uint16",
+          },
+          {
+            internalType: "bytes32",
+            name: "emitterAddress",
+            type: "bytes32",
+          },
+        ],
+        internalType: "struct PythInternalStructs.DataSource",
+        name: "",
+        type: "tuple",
+      },
+    ],
+    stateMutability: "view",
+    type: "function",
+  },
+  {
+    inputs: [
+      {
+        internalType: "bytes",
+        name: "encodedVM",
+        type: "bytes",
+      },
+    ],
+    name: "executeGovernanceInstruction",
+    outputs: [],
+    stateMutability: "nonpayable",
+    type: "function",
+  },
+  {
+    inputs: [],
+    name: "version",
+    outputs: [{ internalType: "string", name: "", type: "string" }],
+    stateMutability: "pure",
+    type: "function",
+  },
+  {
+    inputs: [],
+    name: "singleUpdateFeeInWei",
+    outputs: [
+      {
+        internalType: "uint256",
+        name: "",
+        type: "uint256",
+      },
+    ],
+    stateMutability: "view",
+    type: "function",
+  },
+  {
+    inputs: [],
+    name: "validDataSources",
+    outputs: [
+      {
+        components: [
+          {
+            internalType: "uint16",
+            name: "chainId",
+            type: "uint16",
+          },
+          {
+            internalType: "bytes32",
+            name: "emitterAddress",
+            type: "bytes32",
+          },
+        ],
+        internalType: "struct PythInternalStructs.DataSource[]",
+        name: "",
+        type: "tuple[]",
+      },
+    ],
+    stateMutability: "view",
+    type: "function",
+    constant: true,
+  },
+  {
+    inputs: [],
+    name: "lastExecutedGovernanceSequence",
+    outputs: [
+      {
+        internalType: "uint64",
+        name: "",
+        type: "uint64",
+      },
+    ],
+    stateMutability: "view",
+    type: "function",
+  },
+  {
+    inputs: [
+      {
+        internalType: "bytes32",
+        name: "id",
+        type: "bytes32",
+      },
+    ],
+    name: "priceFeedExists",
+    outputs: [
+      {
+        internalType: "bool",
+        name: "",
+        type: "bool",
+      },
+    ],
+    stateMutability: "view",
+    type: "function",
+  },
+  ...PythInterfaceAbi,
+] as any;
+
+const WORMHOLE_ABI = [
+  {
+    inputs: [],
+    name: "getCurrentGuardianSetIndex",
+    outputs: [
+      {
+        internalType: "uint32",
+        name: "",
+        type: "uint32",
+      },
+    ],
+    stateMutability: "view",
+    type: "function",
+  },
+  {
+    inputs: [
+      {
+        internalType: "uint32",
+        name: "index",
+        type: "uint32",
+      },
+    ],
+    name: "getGuardianSet",
+    outputs: [
+      {
+        components: [
+          {
+            internalType: "address[]",
+            name: "keys",
+            type: "address[]",
+          },
+          {
+            internalType: "uint32",
+            name: "expirationTime",
+            type: "uint32",
+          },
+        ],
+        internalType: "struct Structs.GuardianSet",
+        name: "",
+        type: "tuple",
+      },
+    ],
+    stateMutability: "view",
+    type: "function",
+  },
+] as any;
+export class WormholeEvmContract {
+  constructor(public chain: EvmChain, public address: string) {}
+  getContract() {
+    const web3 = new Web3(this.chain.getRpcUrl());
+    return new web3.eth.Contract(WORMHOLE_ABI, this.address);
+  }
+
+  /**
+   * Returns an array of guardian addresses used for VAA verification in this contract
+   */
+  async getGuardianSet(): Promise<string[]> {
+    const wormholeContract = this.getContract();
+    const currentIndex = await wormholeContract.methods
+      .getCurrentGuardianSetIndex()
+      .call();
+    const [currentSet] = await wormholeContract.methods
+      .getGuardianSet(currentIndex)
+      .call();
+    return currentSet;
+  }
+}
+
+export class EvmContract extends Contract {
+  static type = "EvmContract";
+
+  constructor(public chain: EvmChain, public address: string) {
+    super();
+  }
+
+  static fromJson(chain: Chain, parsed: any): EvmContract {
+    if (parsed.type !== EvmContract.type) throw new Error("Invalid type");
+    if (!(chain instanceof EvmChain))
+      throw new Error(`Wrong chain type ${chain}`);
+    return new EvmContract(chain, parsed.address);
+  }
+
+  getId(): string {
+    return `${this.chain.getId()}_${this.address}`;
+  }
+
+  getType(): string {
+    return EvmContract.type;
+  }
+
+  async getVersion(): Promise<string> {
+    const pythContract = this.getContract();
+    const result = await pythContract.methods.version().call();
+    return result;
+  }
+
+  static async deploy(
+    chain: EvmChain,
+    privateKey: string,
+    abi: any,
+    bytecode: string
+  ): Promise<EvmContract> {
+    const web3 = new Web3(chain.getRpcUrl());
+    const signer = web3.eth.accounts.privateKeyToAccount(privateKey);
+    web3.eth.accounts.wallet.add(signer);
+    const contract = new web3.eth.Contract(abi);
+    const deployTx = contract.deploy({ data: bytecode });
+    const gas = await deployTx.estimateGas();
+    const gasPrice = await chain.getGasPrice();
+    const deployerBalance = await web3.eth.getBalance(signer.address);
+    const gasDiff = BigInt(gas) * BigInt(gasPrice) - BigInt(deployerBalance);
+    if (gasDiff > 0n) {
+      throw new Error(
+        `Insufficient funds to deploy contract. Need ${gas} (gas) * ${gasPrice} (gasPrice)= ${
+          BigInt(gas) * BigInt(gasPrice)
+        } wei, but only have ${deployerBalance} wei. We need ${
+          Number(gasDiff) / 10 ** 18
+        } ETH more.`
+      );
+    }
+
+    const deployedContract = await deployTx.send({
+      from: signer.address,
+      gas,
+      gasPrice,
+    });
+    return new EvmContract(chain, deployedContract.options.address);
+  }
+
+  getContract() {
+    const web3 = new Web3(this.chain.getRpcUrl());
+    const pythContract = new web3.eth.Contract(EXTENDED_PYTH_ABI, this.address);
+    return pythContract;
+  }
+
+  /**
+   * Returns the bytecode of the contract in hex format
+   */
+  async getCode(): Promise<string> {
+    // TODO: handle proxy contracts
+    const web3 = new Web3(this.chain.getRpcUrl());
+    return web3.eth.getCode(this.address);
+  }
+
+  /**
+   * Returns the keccak256 digest of the contract bytecode after replacing any occurrences of the contract addr in
+   * the bytecode with 0.The bytecode stores the deployment address as an immutable variable.
+   * This behavior is inherited from OpenZeppelin's implementation of UUPSUpgradeable contract.
+   * You can read more about verification with immutable variables here:
+   * https://docs.sourcify.dev/docs/immutables/
+   * This function can be used to verify that the contract code is the same on all chains and matches
+   * with the deployedCode property generated by truffle builds
+   */
+  async getCodeDigestWithoutAddress(): Promise<string> {
+    const code = await this.getCode();
+    const strippedCode = code.replaceAll(
+      this.address.toLowerCase().replace("0x", ""),
+      "0000000000000000000000000000000000000000"
+    );
+    return Web3.utils.keccak256(strippedCode);
+  }
+
+  async getLastExecutedGovernanceSequence() {
+    const pythContract = await this.getContract();
+    return Number(
+      await pythContract.methods.lastExecutedGovernanceSequence().call()
+    );
+  }
+
+  async getPriceFeed(feedId: string) {
+    const pythContract = this.getContract();
+    const feed = "0x" + feedId;
+    const exists = await pythContract.methods.priceFeedExists(feed).call();
+    if (!exists) {
+      return undefined;
+    }
+    const [price, conf, expo, publishTime] = await pythContract.methods
+      .getPriceUnsafe(feed)
+      .call();
+
+    const [emaPrice, emaConf, emaExpo, emaPublishTime] =
+      await pythContract.methods.getEmaPriceUnsafe(feed).call();
+    return {
+      price: { price, conf, expo, publishTime },
+      emaPrice: {
+        price: emaPrice,
+        conf: emaConf,
+        expo: emaExpo,
+        publishTime: emaPublishTime,
+      },
+    };
+  }
+
+  async getValidTimePeriod() {
+    const pythContract = this.getContract();
+    const result = await pythContract.methods.getValidTimePeriod().call();
+    return Number(result);
+  }
+
+  /**
+   * Returns the wormhole contract which is being used for VAA verification
+   */
+  async getWormholeContract(): Promise<WormholeEvmContract> {
+    const pythContract = this.getContract();
+    const address = await pythContract.methods.wormhole().call();
+    return new WormholeEvmContract(this.chain, address);
+  }
+
+  async getBaseUpdateFee() {
+    const pythContract = this.getContract();
+    const result = await pythContract.methods.singleUpdateFeeInWei().call();
+    return { amount: result };
+  }
+
+  async getDataSources(): Promise<DataSource[]> {
+    const pythContract = this.getContract();
+    const result = await pythContract.methods.validDataSources().call();
+    return result.map(
+      ({
+        chainId,
+        emitterAddress,
+      }: {
+        chainId: string;
+        emitterAddress: string;
+      }) => {
+        return {
+          emitterChain: Number(chainId),
+          emitterAddress: emitterAddress.replace("0x", ""),
+        };
+      }
+    );
+  }
+
+  async getGovernanceDataSource(): Promise<DataSource> {
+    const pythContract = this.getContract();
+    const [chainId, emitterAddress] = await pythContract.methods
+      .governanceDataSource()
+      .call();
+    return {
+      emitterChain: Number(chainId),
+      emitterAddress: emitterAddress.replace("0x", ""),
+    };
+  }
+
+  async executeUpdatePriceFeed(senderPrivateKey: string, vaas: Buffer[]) {
+    const web3 = new Web3(this.chain.getRpcUrl());
+    const { address } = web3.eth.accounts.wallet.add(senderPrivateKey);
+    const pythContract = new web3.eth.Contract(EXTENDED_PYTH_ABI, this.address);
+    const priceFeedUpdateData = vaas.map((vaa) => "0x" + vaa.toString("hex"));
+    const updateFee = await pythContract.methods
+      .getUpdateFee(priceFeedUpdateData)
+      .call();
+    const transactionObject =
+      pythContract.methods.updatePriceFeeds(priceFeedUpdateData);
+    const gasEstiamte = await transactionObject.estimateGas({
+      from: address,
+      gas: 100000000,
+      value: updateFee,
+    });
+    return transactionObject.send({
+      from: address,
+      value: updateFee,
+      gas: gasEstiamte * GAS_ESTIMATE_MULTIPLIER,
+      gasPrice: await this.chain.getGasPrice(),
+    });
+  }
+
+  async executeGovernanceInstruction(senderPrivateKey: string, vaa: Buffer) {
+    const web3 = new Web3(this.chain.getRpcUrl());
+    const { address } = web3.eth.accounts.wallet.add(senderPrivateKey);
+    const pythContract = new web3.eth.Contract(EXTENDED_PYTH_ABI, this.address);
+    const transactionObject = pythContract.methods.executeGovernanceInstruction(
+      "0x" + vaa.toString("hex")
+    );
+    const gasEstiamte = await transactionObject.estimateGas({
+      from: address,
+      gas: 100000000,
+    });
+    return transactionObject.send({
+      from: address,
+      gas: gasEstiamte * GAS_ESTIMATE_MULTIPLIER,
+      gasPrice: await this.chain.getGasPrice(),
+    });
+  }
+
+  getChain(): EvmChain {
+    return this.chain;
+  }
+
+  toJson() {
+    return {
+      chain: this.chain.getId(),
+      address: this.address,
+      type: EvmContract.type,
+    };
+  }
+}

+ 53 - 19
contract_manager/src/sui.ts → contract_manager/src/contracts/sui.ts

@@ -7,9 +7,9 @@ import {
   SUI_CLOCK_OBJECT_ID,
   TransactionBlock,
 } from "@mysten/sui.js";
-import { Chain, SuiChain } from "./chains";
-import { DataSource, HexString32Bytes } from "@pythnetwork/xc-governance-sdk";
-import { Contract } from "./base";
+import { Chain, SuiChain } from "../chains";
+import { DataSource } from "xc_admin_common";
+import { Contract } from "../base";
 
 export class SuiContract extends Contract {
   static type = "SuiContract";
@@ -47,7 +47,7 @@ export class SuiContract extends Contract {
 
   toJson() {
     return {
-      chain: this.chain.id,
+      chain: this.chain.getId(),
       stateId: this.stateId,
       wormholeStateId: this.wormholeStateId,
       type: SuiContract.type,
@@ -112,6 +112,26 @@ export class SuiContract extends Contract {
     return result.data.objectId;
   }
 
+  private async parsePrice(priceInfo: any) {
+    const packageId = await this.getPythPackageId();
+    const expectedType = `${packageId}::price::Price`;
+    if (priceInfo.type !== expectedType) {
+      throw new Error(
+        `Price type mismatch, expected ${expectedType} but found ${priceInfo.type}`
+      );
+    }
+    let expo = priceInfo.fields.expo.fields.magnitude;
+    if (priceInfo.fields.expo.fields.negative) expo = "-" + expo;
+    let price = priceInfo.fields.price.fields.magnitude;
+    if (priceInfo.fields.price.fields.negative) price = "-" + price;
+    return {
+      conf: priceInfo.fields.conf,
+      publishTime: priceInfo.fields.timestamp,
+      expo,
+      price,
+    };
+  }
+
   async getPriceFeed(feedId: string) {
     const tableId = await this.getPriceTableId();
     const provider = this.getProvider();
@@ -125,7 +145,7 @@ export class SuiContract extends Contract {
       },
     });
     if (!result.data || !result.data.content) {
-      throw new Error("Price feed not found");
+      return undefined;
     }
     if (result.data.content.dataType !== "moveObject") {
       throw new Error("Price feed type mismatch");
@@ -145,7 +165,15 @@ export class SuiContract extends Contract {
         `Expected ${priceInfoObjectId} to be a moveObject (PriceInfoObject)`
       );
     }
-    return priceInfo.data.content.fields;
+    return {
+      emaPrice: await this.parsePrice(
+        priceInfo.data.content.fields.price_info.fields.price_feed.fields
+          .ema_price
+      ),
+      price: await this.parsePrice(
+        priceInfo.data.content.fields.price_info.fields.price_feed.fields.price
+      ),
+    };
   }
 
   /**
@@ -167,7 +195,10 @@ export class SuiContract extends Contract {
     return this.executeTransaction(tx, keypair);
   }
 
-  async executeGovernanceInstruction(keypair: Ed25519Keypair, vaa: Buffer) {
+  async executeGovernanceInstruction(senderPrivateKey: string, vaa: Buffer) {
+    const keypair = Ed25519Keypair.fromSecretKey(
+      Buffer.from(senderPrivateKey, "hex")
+    );
     const tx = new TransactionBlock();
     const packageId = await this.getPythPackageId();
     let decreeReceipt = await this.getVaaDecreeReceipt(tx, packageId, vaa);
@@ -321,14 +352,12 @@ export class SuiContract extends Contract {
     }
     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")
-          )
-        );
+        return {
+          emitterChain: Number(fields.emitter_chain),
+          emitterAddress: Buffer.from(
+            fields.emitter_address.fields.value.fields.data
+          ).toString("hex"),
+        };
       }
     );
   }
@@ -339,10 +368,10 @@ export class SuiContract extends Contract {
     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"))
-    );
+    return {
+      emitterChain: Number(chainId),
+      emitterAddress: Buffer.from(emitterAddress).toString("hex"),
+    };
   }
 
   async getBaseUpdateFee() {
@@ -350,6 +379,11 @@ export class SuiContract extends Contract {
     return { amount: fields.base_update_fee };
   }
 
+  async getLastExecutedGovernanceSequence() {
+    const fields = await this.getStateFields();
+    return Number(fields.last_executed_governance_sequence);
+  }
+
   private getProvider() {
     return new JsonRpcProvider(new Connection({ fullnode: this.chain.rpcUrl }));
   }

+ 0 - 183
contract_manager/src/evm.ts

@@ -1,183 +0,0 @@
-import Web3 from "web3"; //TODO: decide on using web3 or ethers.js
-import PythInterfaceAbi from "@pythnetwork/pyth-sdk-solidity/abis/IPyth.json";
-import { Contract } from "./base";
-import { Chain, EVMChain } from "./chains";
-import { DataSource, HexString32Bytes } from "@pythnetwork/xc-governance-sdk";
-
-const EXTENDED_PYTH_ABI = [
-  {
-    inputs: [],
-    name: "governanceDataSource",
-    outputs: [
-      {
-        components: [
-          {
-            internalType: "uint16",
-            name: "chainId",
-            type: "uint16",
-          },
-          {
-            internalType: "bytes32",
-            name: "emitterAddress",
-            type: "bytes32",
-          },
-        ],
-        internalType: "struct PythInternalStructs.DataSource",
-        name: "",
-        type: "tuple",
-      },
-    ],
-    stateMutability: "view",
-    type: "function",
-  },
-  {
-    inputs: [
-      {
-        internalType: "bytes",
-        name: "encodedVM",
-        type: "bytes",
-      },
-    ],
-    name: "executeGovernanceInstruction",
-    outputs: [],
-    stateMutability: "nonpayable",
-    type: "function",
-  },
-  {
-    inputs: [],
-    name: "singleUpdateFeeInWei",
-    outputs: [
-      {
-        internalType: "uint256",
-        name: "",
-        type: "uint256",
-      },
-    ],
-    stateMutability: "view",
-    type: "function",
-  },
-  {
-    inputs: [],
-    name: "validDataSources",
-    outputs: [
-      {
-        components: [
-          {
-            internalType: "uint16",
-            name: "chainId",
-            type: "uint16",
-          },
-          {
-            internalType: "bytes32",
-            name: "emitterAddress",
-            type: "bytes32",
-          },
-        ],
-        internalType: "struct PythInternalStructs.DataSource[]",
-        name: "",
-        type: "tuple[]",
-      },
-    ],
-    stateMutability: "view",
-    type: "function",
-    constant: true,
-  },
-  ...PythInterfaceAbi,
-] as any;
-
-export class EVMContract extends Contract {
-  static type = "EVMContract";
-
-  constructor(public chain: EVMChain, public address: string) {
-    super();
-  }
-
-  static fromJson(chain: Chain, parsed: any): EVMContract {
-    if (parsed.type !== EVMContract.type) throw new Error("Invalid type");
-    if (!(chain instanceof EVMChain))
-      throw new Error(`Wrong chain type ${chain}`);
-    return new EVMContract(chain, parsed.address);
-  }
-
-  getId(): string {
-    return `${this.chain.getId()}_${this.address}`;
-  }
-
-  getType(): string {
-    return EVMContract.type;
-  }
-
-  getContract() {
-    const web3 = new Web3(this.chain.rpcUrl);
-    const pythContract = new web3.eth.Contract(EXTENDED_PYTH_ABI, this.address);
-    return pythContract;
-  }
-
-  async getPriceFeed(feedId: string) {
-    const pythContract = this.getContract();
-    const [price, conf, expo, publishTime] = await pythContract.methods
-      .getPriceUnsafe(feedId)
-      .call();
-    return { price, conf, expo, publishTime };
-  }
-
-  async getValidTimePeriod() {
-    const pythContract = this.getContract();
-    const result = await pythContract.methods.getValidTimePeriod().call();
-    return Number(result);
-  }
-
-  async getBaseUpdateFee() {
-    const pythContract = this.getContract();
-    const result = await pythContract.methods.singleUpdateFeeInWei().call();
-    return { amount: result };
-  }
-
-  async getDataSources(): Promise<DataSource[]> {
-    const pythContract = this.getContract();
-    const result = await pythContract.methods.validDataSources().call();
-    return result.map(({ chainId, emitterAddress }: 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)
-    );
-  }
-
-  async executeGovernanceInstruction(privateKey: string, vaa: Buffer) {
-    const web3 = new Web3(this.chain.rpcUrl);
-    const { address } = web3.eth.accounts.wallet.add(privateKey);
-    const pythContract = new web3.eth.Contract(EXTENDED_PYTH_ABI, this.address);
-    const transactionObject = pythContract.methods.executeGovernanceInstruction(
-      "0x" + vaa.toString("hex")
-    );
-    const gasEstiamte = await transactionObject.estimateGas({
-      from: address,
-      gas: 100000000,
-    });
-    return transactionObject.send({ from: address, gas: gasEstiamte * 2 });
-  }
-
-  getChain(): EVMChain {
-    return this.chain;
-  }
-
-  toJson() {
-    return {
-      chain: this.chain.id,
-      address: this.address,
-      type: EVMContract.type,
-    };
-  }
-}

+ 55 - 13
contract_manager/src/entities.ts → contract_manager/src/governance.ts

@@ -20,6 +20,7 @@ import SquadsMesh from "@sqds/mesh";
 import { AnchorProvider, Wallet } from "@coral-xyz/anchor/dist/cjs/provider";
 import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet";
 import {
+  decodeGovernancePayload,
   executeProposal,
   MultisigVault,
   WORMHOLE_ADDRESS,
@@ -32,6 +33,8 @@ import {
   deriveWormholeBridgeDataKey,
 } from "@certusone/wormhole-sdk/lib/cjs/solana/wormhole";
 import { Storable } from "./base";
+import { parseVaa } from "@certusone/wormhole-sdk";
+import { DefaultStore } from "./store";
 
 class InvalidTransactionError extends Error {
   constructor(message: string) {
@@ -44,7 +47,7 @@ export class SubmittedWormholeMessage {
   constructor(
     public emitter: PublicKey,
     public sequenceNumber: number,
-    public cluster: string
+    public cluster: PythCluster
   ) {}
 
   /**
@@ -104,8 +107,7 @@ export class SubmittedWormholeMessage {
    * @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 rpcUrl = WORMHOLE_API_ENDPOINT[this.cluster];
 
     let startTime = Date.now();
     while (Date.now() - startTime < waitingSeconds * 1000) {
@@ -125,6 +127,33 @@ export class SubmittedWormholeMessage {
   }
 }
 
+/**
+ * A general executor that tries to find any contract that can execute a given VAA and executes it
+ * @param senderPrivateKey the private key to execute the governance instruction with
+ * @param vaa the VAA to execute
+ */
+export async function executeVaa(senderPrivateKey: string, vaa: Buffer) {
+  const parsedVaa = parseVaa(vaa);
+  const action = decodeGovernancePayload(parsedVaa.payload);
+  if (!action) return; //TODO: handle other actions
+  for (const contract of Object.values(DefaultStore.contracts)) {
+    if (
+      action.targetChainId === "unset" ||
+      contract.getChain().wormholeChainName === action.targetChainId
+    ) {
+      const governanceSource = await contract.getGovernanceDataSource();
+      if (
+        governanceSource.emitterAddress ===
+          parsedVaa.emitterAddress.toString("hex") &&
+        governanceSource.emitterChain === parsedVaa.emitterChain
+      ) {
+        // TODO: check governance sequence number as well
+        await contract.executeGovernanceInstruction(senderPrivateKey, vaa);
+      }
+    }
+  }
+}
+
 function asPythCluster(cluster: string): PythCluster {
   const pythCluster = cluster as PythCluster;
   getPythClusterApiUrl(pythCluster); // throws if cluster is invalid
@@ -146,6 +175,10 @@ export class WormholeEmitter {
     this.cluster = asPythCluster(cluster);
   }
 
+  public getEmitter() {
+    return this.wallet.publicKey;
+  }
+
   async sendMessage(payload: Buffer) {
     const provider = new AnchorProvider(
       new Connection(getPythClusterApiUrl(this.cluster), "confirmed"),
@@ -206,26 +239,32 @@ export class WormholeMultiSigTransaction {
 
   async getState() {
     const proposal = await this.squad.getTransaction(this.address);
-    return proposal.status; //TODO: make it typed and parse the status to a more readable format
+    // Converts the status object to a string e.g
+    // { "active":{} } => "active"
+    return Object.keys(proposal.status)[0];
   }
 
-  async execute(): Promise<SubmittedWormholeMessage> {
+  async execute(): Promise<SubmittedWormholeMessage[]> {
     const proposal = await this.squad.getTransaction(this.address);
     const signatures = await executeProposal(
       proposal,
       this.squad,
       this.cluster
     );
+    const msgs: SubmittedWormholeMessage[] = [];
     for (const signature of signatures) {
       try {
-        return SubmittedWormholeMessage.fromTransactionSignature(
-          signature,
-          this.cluster
+        msgs.push(
+          await SubmittedWormholeMessage.fromTransactionSignature(
+            signature,
+            this.cluster
+          )
         );
       } catch (e: any) {
         if (!(e instanceof InvalidTransactionError)) throw e;
       }
     }
+    if (msgs.length > 0) return msgs;
     throw new Error("No transactions with wormhole messages found");
   }
 }
@@ -284,7 +323,8 @@ export class Vault extends Storable {
   }
 
   public async proposeWormholeMessage(
-    payload: Buffer
+    payloads: Buffer[],
+    proposalAddress?: PublicKey
   ): Promise<WormholeMultiSigTransaction> {
     const squad = this.getSquadOrThrow();
     const multisigVault = new MultisigVault(
@@ -293,10 +333,12 @@ export class Vault extends Storable {
       squad,
       this.key
     );
-    const txAccount = await multisigVault.proposeWormholeMessageWithPayer(
-      payload,
-      squad.wallet.publicKey
-    );
+    const txAccount =
+      await multisigVault.proposeWormholeMultipleMessagesWithPayer(
+        payloads,
+        squad.wallet.publicKey,
+        proposalAddress
+      );
     return new WormholeMultiSigTransaction(txAccount, squad, this.cluster);
   }
 }

+ 6 - 6
contract_manager/src/shell.ts

@@ -5,12 +5,12 @@ const service = tsNode.create({ ...repl.evalAwarePartialHost });
 repl.setService(service);
 repl.start();
 repl.evalCode(
-  "import { loadHotWallet, Vault } from './src/entities';" +
-    "import { SuiChain, CosmWasmChain, AptosChain, EVMChain } from './src/chains';" +
-    "import { SuiContract } from './src/sui';" +
-    "import { CosmWasmContract } from './src/cosmwasm';" +
-    "import { EVMContract } from './src/evm';" +
-    "import { AptosContract } from './src/aptos';" +
+  "import { loadHotWallet, Vault } from './src/governance';" +
+    "import { SuiChain, CosmWasmChain, AptosChain, EvmChain } from './src/chains';" +
+    "import { SuiContract } from './src/contracts/sui';" +
+    "import { CosmWasmContract } from './src/contracts/cosmwasm';" +
+    "import { EvmContract } from './src/contracts/evm';" +
+    "import { AptosContract } from './src/contracts/aptos';" +
     "import { DefaultStore } from './src/store';" +
     "DefaultStore"
 );

+ 16 - 9
contract_manager/src/store.ts

@@ -1,6 +1,13 @@
-import { AptosChain, Chain, CosmWasmChain, EVMChain, SuiChain } from "./chains";
-import { CosmWasmContract } from "./cosmwasm";
-import { SuiContract } from "./sui";
+import {
+  AptosChain,
+  Chain,
+  CosmWasmChain,
+  EvmChain,
+  GlobalChain,
+  SuiChain,
+} from "./chains";
+import { CosmWasmContract } from "./contracts/cosmwasm";
+import { SuiContract } from "./contracts/sui";
 import { Contract } from "./base";
 import { parse, stringify } from "yaml";
 import {
@@ -11,12 +18,12 @@ import {
   statSync,
   writeFileSync,
 } from "fs";
-import { EVMContract } from "./evm";
-import { AptosContract } from "./aptos";
-import { Vault } from "./entities";
+import { EvmContract } from "./contracts/evm";
+import { AptosContract } from "./contracts/aptos";
+import { Vault } from "./governance";
 
 class Store {
-  public chains: Record<string, Chain> = {};
+  public chains: Record<string, Chain> = { global: new GlobalChain() };
   public contracts: Record<string, Contract> = {};
   public vaults: Record<string, Vault> = {};
 
@@ -76,7 +83,7 @@ class Store {
     let allChainClasses = {
       [CosmWasmChain.type]: CosmWasmChain,
       [SuiChain.type]: SuiChain,
-      [EVMChain.type]: EVMChain,
+      [EvmChain.type]: EvmChain,
       [AptosChain.type]: AptosChain,
     };
 
@@ -94,7 +101,7 @@ class Store {
     let allContractClasses = {
       [CosmWasmContract.type]: CosmWasmContract,
       [SuiContract.type]: SuiContract,
-      [EVMContract.type]: EVMContract,
+      [EvmContract.type]: EvmContract,
       [AptosContract.type]: AptosContract,
     };
     this.getYamlFiles(`${this.path}/contracts/`).forEach((yamlFile) => {

+ 4 - 31
contract_manager/src/test.ts

@@ -1,37 +1,10 @@
 import { DefaultStore } from "./store";
 
 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(DefaultStore.contracts).forEach((c) => {
-    console.log(c);
-    s.save(c);
-  });
-
-  Object.values(DefaultStore.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);
+  for (const contract of Object.values(DefaultStore.contracts)) {
+    console.log("Contract", contract.getId());
+    console.log(await contract.getGovernanceDataSource());
+  }
 }
 
 test();

+ 5 - 0
contract_manager/store/chains/AptosChain/aptos_mainnet.yaml

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

+ 2 - 0
contract_manager/store/chains/AptosChain/aptos_testnet.yaml

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

+ 5 - 0
contract_manager/store/chains/CosmWasmChain/injective.yaml

@@ -0,0 +1,5 @@
+querierEndpoint: https://injective-rpc.quickapi.com:443
+id: injective
+wormholeChainName: injective
+mainnet: true
+type: CosmWasmChain

+ 5 - 0
contract_manager/store/chains/CosmWasmChain/injective_testnet.yaml

@@ -0,0 +1,5 @@
+querierEndpoint: https://k8s.testnet.tm.injective.network:443
+id: injective_testnet
+wormholeChainName: injective_testnet
+mainnet: false
+type: CosmWasmChain

+ 2 - 0
contract_manager/store/chains/CosmWasmChain/juno_testnet.yaml

@@ -1,6 +1,8 @@
 querierEndpoint: https://rpc.uni.junonetwork.io/
 executorEndpoint: https://rpc.uni.junonetwork.io/
 id: juno_testnet
+wormholeChainName: juno_testnet
+mainnet: false
 gasPrice: "0.025"
 prefix: juno
 feeDenom: ujunox

+ 2 - 0
contract_manager/store/chains/CosmWasmChain/neutron.yaml

@@ -1,6 +1,8 @@
 querierEndpoint: https://rpc-kralum.neutron-1.neutron.org
 executorEndpoint: https://rpc-kralum.neutron-1.neutron.org
 id: neutron
+wormholeChainName: neutron
+mainnet: true
 gasPrice: "0.025"
 prefix: neutron
 feeDenom: untrn

+ 4 - 2
contract_manager/store/chains/CosmWasmChain/neutron_testnet_pion_1.yaml

@@ -1,6 +1,8 @@
-querierEndpoint: https://rpc.pion.rs-testnet.polypore.xyz/
-executorEndpoint: https://rpc.pion.rs-testnet.polypore.xyz/
+querierEndpoint: https://rpc-palvus.pion-1.ntrn.tech/
+executorEndpoint: https://rpc-palvus.pion-1.ntrn.tech/
 id: neutron_testnet_pion_1
+wormholeChainName: neutron_testnet_pion_1
+mainnet: false
 gasPrice: "0.05"
 prefix: neutron
 feeDenom: untrn

+ 9 - 0
contract_manager/store/chains/CosmWasmChain/osmosis.yaml

@@ -0,0 +1,9 @@
+querierEndpoint: https://rpc.osmosis.zone:443
+executorEndpoint: https://rpc.osmosis.zone:443
+id: osmosis
+wormholeChainName: osmosis
+mainnet: true
+gasPrice: "0.025"
+prefix: osmo
+feeDenom: uosmo
+type: CosmWasmChain

+ 2 - 0
contract_manager/store/chains/CosmWasmChain/osmosis_testnet_5.yaml

@@ -1,6 +1,8 @@
 querierEndpoint: https://rpc.osmotest5.osmosis.zone/
 executorEndpoint: https://rpc.osmotest5.osmosis.zone/
 id: osmosis_testnet_5
+wormholeChainName: osmosis_testnet_5
+mainnet: false
 gasPrice: "0.025"
 prefix: osmo
 feeDenom: uosmo

+ 2 - 0
contract_manager/store/chains/CosmWasmChain/sei_pacific_1.yaml

@@ -1,6 +1,8 @@
 querierEndpoint: https://sei-rpc.polkachu.com
 executorEndpoint: https://sei-rpc.polkachu.com
 id: sei_pacific_1
+wormholeChainName: sei_pacific_1
+mainnet: true
 gasPrice: "0.025"
 prefix: sei
 feeDenom: usei

+ 3 - 1
contract_manager/store/chains/CosmWasmChain/sei_testnet_atlantic_2.yaml

@@ -1,7 +1,9 @@
 querierEndpoint: https://rpc.atlantic-2.seinetwork.io/
 executorEndpoint: https://rpc.atlantic-2.seinetwork.io/
 id: sei_testnet_atlantic_2
-gasPrice: "0.01"
+wormholeChainName: sei_testnet_atlantic_2
+mainnet: false
+gasPrice: "0.10"
 prefix: sei
 feeDenom: usei
 type: CosmWasmChain

+ 0 - 3
contract_manager/store/chains/EVMChain/arbitrum_testnet.yaml

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

+ 0 - 3
contract_manager/store/chains/EVMChain/cronos.yaml

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

+ 0 - 3
contract_manager/store/chains/EVMChain/cronos_testnet.yaml

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

+ 6 - 0
contract_manager/store/chains/EvmChain/arbitrum.yaml

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

+ 6 - 0
contract_manager/store/chains/EvmChain/arbitrum_testnet.yaml

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

+ 6 - 0
contract_manager/store/chains/EvmChain/aurora.yaml

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

+ 6 - 0
contract_manager/store/chains/EvmChain/aurora_testnet.yaml

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

+ 6 - 0
contract_manager/store/chains/EvmChain/avalanche.yaml

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

+ 6 - 0
contract_manager/store/chains/EvmChain/base.yaml

@@ -0,0 +1,6 @@
+id: base
+wormholeChainName: base
+mainnet: true
+rpcUrl: https://developer-access-mainnet.base.org/
+networkId: 8453
+type: EvmChain

+ 6 - 0
contract_manager/store/chains/EvmChain/base_goerli.yaml

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

+ 6 - 0
contract_manager/store/chains/EvmChain/bnb.yaml

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

+ 6 - 0
contract_manager/store/chains/EvmChain/bnb_testnet.yaml

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

+ 6 - 0
contract_manager/store/chains/EvmChain/canto.yaml

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

+ 6 - 0
contract_manager/store/chains/EvmChain/canto_testnet.yaml

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

+ 6 - 0
contract_manager/store/chains/EvmChain/celo.yaml

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

+ 6 - 0
contract_manager/store/chains/EvmChain/celo_alfajores_testnet.yaml

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

+ 6 - 0
contract_manager/store/chains/EvmChain/chiado.yaml

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

+ 6 - 0
contract_manager/store/chains/EvmChain/conflux_espace.yaml

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

+ 6 - 0
contract_manager/store/chains/EvmChain/conflux_espace_testnet.yaml

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

+ 6 - 0
contract_manager/store/chains/EvmChain/cronos.yaml

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

+ 6 - 0
contract_manager/store/chains/EvmChain/cronos_testnet.yaml

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

+ 6 - 0
contract_manager/store/chains/EvmChain/eos.yaml

@@ -0,0 +1,6 @@
+id: eos
+wormholeChainName: eos
+mainnet: true
+rpcUrl: https://api.evm.eosnetwork.com
+networkId: 17777
+type: EvmChain

+ 6 - 0
contract_manager/store/chains/EvmChain/eos_testnet.yaml

@@ -0,0 +1,6 @@
+id: eos_testnet
+wormholeChainName: eos
+mainnet: false
+rpcUrl: https://api.testnet.evm.eosnetwork.com
+networkId: 15557
+type: EvmChain

+ 6 - 0
contract_manager/store/chains/EvmChain/ethereum.yaml

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

+ 6 - 0
contract_manager/store/chains/EvmChain/evmos.yaml

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

+ 6 - 0
contract_manager/store/chains/EvmChain/evmos_testnet.yaml

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

+ 6 - 0
contract_manager/store/chains/EvmChain/fantom.yaml

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

+ 6 - 0
contract_manager/store/chains/EvmChain/fantom_testnet.yaml

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

+ 6 - 0
contract_manager/store/chains/EvmChain/fuji.yaml

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

+ 6 - 0
contract_manager/store/chains/EvmChain/gnosis.yaml

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

+ 6 - 0
contract_manager/store/chains/EvmChain/goerli.yaml

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

+ 6 - 0
contract_manager/store/chains/EvmChain/kava.yaml

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

+ 6 - 0
contract_manager/store/chains/EvmChain/kava_testnet.yaml

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

+ 6 - 0
contract_manager/store/chains/EvmChain/kcc.yaml

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

+ 6 - 0
contract_manager/store/chains/EvmChain/kcc_testnet.yaml

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

+ 6 - 0
contract_manager/store/chains/EvmChain/linea.yaml

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

+ 6 - 0
contract_manager/store/chains/EvmChain/linea_goerli.yaml

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

+ 6 - 0
contract_manager/store/chains/EvmChain/mantle.yaml

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

+ 6 - 0
contract_manager/store/chains/EvmChain/mantle_testnet.yaml

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

+ 6 - 0
contract_manager/store/chains/EvmChain/meter.yaml

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

+ 6 - 0
contract_manager/store/chains/EvmChain/meter_testnet.yaml

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

+ 6 - 0
contract_manager/store/chains/EvmChain/mumbai.yaml

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

+ 6 - 0
contract_manager/store/chains/EvmChain/neon.yaml

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

+ 6 - 0
contract_manager/store/chains/EvmChain/neon_devnet.yaml

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

+ 6 - 0
contract_manager/store/chains/EvmChain/optimism.yaml

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

+ 6 - 0
contract_manager/store/chains/EvmChain/optimism_goerli.yaml

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

+ 6 - 0
contract_manager/store/chains/EvmChain/polygon.yaml

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

+ 6 - 0
contract_manager/store/chains/EvmChain/polygon_zkevm.yaml

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

+ 6 - 0
contract_manager/store/chains/EvmChain/polygon_zkevm_testnet.yaml

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

+ 6 - 0
contract_manager/store/chains/EvmChain/shimmer_testnet.yaml

@@ -0,0 +1,6 @@
+id: shimmer_testnet
+wormholeChainName: shimmer
+mainnet: false
+rpcUrl: https://json-rpc.evm.testnet.shimmer.network
+networkId: 1072
+type: EvmChain

+ 6 - 0
contract_manager/store/chains/EvmChain/syndr_nitro_testnet.yaml

@@ -0,0 +1,6 @@
+id: syndr_nitro_testnet
+wormholeChainName: syndr
+mainnet: false
+rpcUrl: https://syndr-nitro-testnet.calderachain.xyz/http
+networkId: 412346
+type: EvmChain

+ 6 - 0
contract_manager/store/chains/EvmChain/wemix.yaml

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

+ 6 - 0
contract_manager/store/chains/EvmChain/wemix_testnet.yaml

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

+ 6 - 0
contract_manager/store/chains/EvmChain/zksync.yaml

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

+ 6 - 0
contract_manager/store/chains/EvmChain/zksync_goerli.yaml

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

+ 2 - 0
contract_manager/store/chains/SuiChain/sui_devnet.yaml

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

+ 2 - 0
contract_manager/store/chains/SuiChain/sui_mainnet.yaml

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

+ 2 - 0
contract_manager/store/chains/SuiChain/sui_testnet.yaml

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

+ 4 - 0
contract_manager/store/contracts/AptosContract/aptos_mainnet_0xbd6d205f2aa288baa71270e66716d3d1bafe173ab9f312de4e9dd761ddef5409.yaml

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

+ 0 - 0
contract_manager/store/contracts/AptosContract/aptos_testnet_0x7e783b349d3e89cf5931af376ebeadbfab855b3fa239b7ada8f5a92fbea6b387.yaml → contract_manager/store/contracts/AptosContract/aptos_testnet_0xbd6d205f2aa288baa71270e66716d3d1bafe173ab9f312de4e9dd761ddef5409.yaml


+ 3 - 0
contract_manager/store/contracts/CosmWasmContract/injective_inj12j43nf2f0qumnt2zrrmpvnsqgzndxefujlvr08.yaml

@@ -0,0 +1,3 @@
+chain: injective
+address: inj12j43nf2f0qumnt2zrrmpvnsqgzndxefujlvr08
+type: CosmWasmContract

+ 3 - 0
contract_manager/store/contracts/CosmWasmContract/injective_testnet_inj18hckkzqf47mdhd734g6papk6wj20y24rm43sk9.yaml

@@ -0,0 +1,3 @@
+chain: injective_testnet
+address: inj18hckkzqf47mdhd734g6papk6wj20y24rm43sk9
+type: CosmWasmContract

+ 3 - 0
contract_manager/store/contracts/CosmWasmContract/injective_testnet_inj18rlflp3735h25jmjx97d22c72sxk260amdjxlu.yaml

@@ -0,0 +1,3 @@
+chain: injective_testnet
+address: inj18rlflp3735h25jmjx97d22c72sxk260amdjxlu
+type: CosmWasmContract

+ 3 - 0
contract_manager/store/contracts/CosmWasmContract/juno_testnet_juno1eacsrua27njc35pxz37y97gmcjs899t59f8pf0rkejjyvtmhws5q6lxsdd.yaml

@@ -0,0 +1,3 @@
+chain: juno_testnet
+address: juno1eacsrua27njc35pxz37y97gmcjs899t59f8pf0rkejjyvtmhws5q6lxsdd
+type: CosmWasmContract

+ 3 - 0
contract_manager/store/contracts/CosmWasmContract/neutron_testnet_pion_1_neutron15ldst8t80982akgr8w8ekcytejzkmfpgdkeq4xgtge48qs7435jqp87u3t.yaml

@@ -0,0 +1,3 @@
+chain: neutron_testnet_pion_1
+address: neutron15ldst8t80982akgr8w8ekcytejzkmfpgdkeq4xgtge48qs7435jqp87u3t
+type: CosmWasmContract

+ 3 - 0
contract_manager/store/contracts/CosmWasmContract/neutron_testnet_pion_1_neutron16zwrmx3zgggmxhzau86xfycm42cr4sj888hdvzsxya3qarp6zhhqzhlkvz.yaml

@@ -0,0 +1,3 @@
+chain: neutron_testnet_pion_1
+address: neutron16zwrmx3zgggmxhzau86xfycm42cr4sj888hdvzsxya3qarp6zhhqzhlkvz
+type: CosmWasmContract

+ 0 - 3
contract_manager/store/contracts/CosmWasmContract/neutron_testnet_pion_1_neutron1xxmcu6wxgawjlajx8jalyk9cxsudnygjg0tvjesfyurh4utvtpes5wmpjp.yaml

@@ -1,3 +0,0 @@
-chain: neutron_testnet_pion_1
-address: neutron1xxmcu6wxgawjlajx8jalyk9cxsudnygjg0tvjesfyurh4utvtpes5wmpjp
-type: CosmWasmContract

+ 3 - 0
contract_manager/store/contracts/CosmWasmContract/osmosis_osmo1hpdzqku55lmfmptpyj6wdlugqs5etr6teqf7r4yqjjrxjznjhtuqqu5kdh.yaml

@@ -0,0 +1,3 @@
+chain: osmosis
+address: osmo13ge29x4e2s63a8ytz2px8gurtyznmue4a69n5275692v3qn3ks8q7cwck7
+type: CosmWasmContract

Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott