瀏覽代碼

[WIP] Pr/drozdziak1/p2w batching/5e704f8b (#877)

* ethereum: p2w contract -> p2w emitter, fill in essential envs

Change-Id: I6fa9364a96738d2cc02ec829a31fedba0586d8e8

commit-id:0a56f1f8

* Add p2w-relay, a p2w-sdk integration test

commit-id:6bfab639

* p2w-sdk: Expand README

Change-Id: I17cb547d6aaddc240588923561c26d11a787df2e

commit-id:6ebd6a22

* p2w-sdk: don't build ETH contracts, only the types

Change-Id: I7cbd18328227700635d7688aa24a9671e8919fcd

commit-id:adf079f7

* p2w: configurability and sane envs

commit-id:f10fd90e

* Solitaire: Implement Option<T> support in structs

commit-id:31aa12d6

* bridge/governance.rs: Stop pestering about EMITTER_ADDRESS

commit-id:d5bd7234

* p2w-attest: price batching

This commit introduces support for multiple Pyth product/price pairs
per call. The initial maximum batch size is 5 and is enforced using a
`P2W_MAX_BATCH_SIZE` constant.

solana/pyth2wormhole/program:
* On-chain batching logic
* Batch message parsing logic

solana/pyth2wormhole/client:
* Off-chain batching logic - divides any number of symbols into
largest possible batches
* Use a multi-symbol config file instead of CLI arguments

third_party/pyth/p2w-sdk:
* Expose batch parsing logic

third_party/pyth/p2w-relay:
* Comment out target chain calls until ETH contract supports batching
* Test the batch parsing function

third_party/pyth/p2w_autoattest.py:
* Generate and use the symbol config file  with pyth2wormhole-client

third_party/pyth/pyth_publisher.py:
* Add a configurable number of mock Pyth symbols
* Adjust HTTP endpoint for multiple symbols

commit-id:73787a61

* p2w-attest: mention attestation size in batch

This commit ensures that no matter the attestation format, a batch
will never contain attestations of different sizes. This guarantee
enables forward compatibility by adding new constant-size fields at
the end of a batch at all times. An older implementation will simply
not consume the remaining newer values while respecting the stated
batch member alignment.

commit-id:210da230

* pyth2wormhole-client: use fresh blockhashes, harden batch errors

This commit makes sure we don't have to deal with expired transactions
due to stale blockhashes. The problem existed with larger symbol
configs as well as on Solana mainnet. Additionally, the attestation logic
now treats transaction errors as non-critical - a failure for a batch
does not prevent attestation attempts for batches farther in the queue

commit-id:5e704f8b
Stanisław Drozd 3 年之前
父節點
當前提交
2ea41b8176
共有 55 個文件被更改,包括 7176 次插入630 次删除
  1. 0 1
      .envrc
  2. 15 1
      Tiltfile
  3. 3 0
      devnet/p2w-attest.yaml
  4. 3 2
      devnet/p2w-relay.yaml
  5. 2 2
      ethereum/.env.template
  6. 2 2
      ethereum/.env.test
  7. 1 1
      ethereum/Dockerfile
  8. 1 1
      ethereum/contracts/pyth/Pyth.sol
  9. 3 3
      ethereum/contracts/pyth/PythGetters.sol
  10. 3 3
      ethereum/contracts/pyth/PythSetters.sol
  11. 2 2
      ethereum/contracts/pyth/PythSetup.sol
  12. 2 2
      ethereum/contracts/pyth/PythState.sol
  13. 1 0
      ethereum/devnet_mnemonic.txt
  14. 5 2
      ethereum/migrations/5_deploy_pyth.js
  15. 5 5
      ethereum/test/pyth.js
  16. 6 1
      solana/bridge/program/src/api/governance.rs
  17. 2 0
      solana/pyth2wormhole/Cargo.lock
  18. 2 0
      solana/pyth2wormhole/client/Cargo.toml
  19. 84 0
      solana/pyth2wormhole/client/src/attestation_cfg.rs
  20. 7 10
      solana/pyth2wormhole/client/src/cli.rs
  21. 38 0
      solana/pyth2wormhole/client/src/config_file.rs
  22. 221 113
      solana/pyth2wormhole/client/src/main.rs
  23. 135 26
      solana/pyth2wormhole/program/src/attest.rs
  24. 13 0
      solana/pyth2wormhole/program/src/config.rs
  25. 243 58
      solana/pyth2wormhole/program/src/types/mod.rs
  26. 10 9
      solana/pyth2wormhole/program/src/wasm.rs
  27. 12 0
      solana/solitaire/client/src/lib.rs
  28. 27 0
      solana/solitaire/program/src/processors/peel.rs
  29. 1 0
      third_party/pyth/Dockerfile.p2w-attest
  30. 1 0
      third_party/pyth/Dockerfile.pyth
  31. 33 0
      third_party/pyth/p2w-relay/.gitignore
  32. 40 0
      third_party/pyth/p2w-relay/Dockerfile
  33. 24 0
      third_party/pyth/p2w-relay/README.md
  34. 5038 0
      third_party/pyth/p2w-relay/package-lock.json
  35. 53 0
      third_party/pyth/p2w-relay/package.json
  36. 2 0
      third_party/pyth/p2w-relay/scripts/copyEthContracts.cjs
  37. 17 0
      third_party/pyth/p2w-relay/scripts/copyEthersTypes.cjs
  38. 17 0
      third_party/pyth/p2w-relay/scripts/copyWasm.cjs
  39. 198 0
      third_party/pyth/p2w-relay/src/index.ts
  40. 15 0
      third_party/pyth/p2w-relay/tsconfig.json
  41. 9 0
      third_party/pyth/p2w-relay/tslint.json
  42. 1 1
      third_party/pyth/p2w-sdk/.gitignore
  43. 10 3
      third_party/pyth/p2w-sdk/README.md
  44. 527 159
      third_party/pyth/p2w-sdk/package-lock.json
  45. 11 9
      third_party/pyth/p2w-sdk/package.json
  46. 2 0
      third_party/pyth/p2w-sdk/scripts/copyEthContracts.cjs
  47. 17 0
      third_party/pyth/p2w-sdk/scripts/copyEthersTypes.cjs
  48. 17 0
      third_party/pyth/p2w-sdk/scripts/copyWasm.cjs
  49. 0 14
      third_party/pyth/p2w-sdk/scripts/copyWasm.js
  50. 39 7
      third_party/pyth/p2w-sdk/src/index.ts
  51. 2 0
      third_party/pyth/p2w-sdk/tsconfig.json
  52. 0 26
      third_party/pyth/p2w-sdk/webpack.config.js
  53. 181 118
      third_party/pyth/p2w_autoattest.py
  54. 46 30
      third_party/pyth/pyth_publisher.py
  55. 27 19
      third_party/pyth/pyth_utils.py

+ 0 - 1
.envrc

@@ -1 +0,0 @@
-eval "$(lorri direnv)"

+ 15 - 1
Tiltfile

@@ -89,7 +89,7 @@ local_resource(
 
 local_resource(
     name = "proto-gen-web",
-    deps = proto_deps,
+    deps = proto_deps + ["buf.gen.web.yaml"],
     resource_deps = ["proto-gen"],
     cmd = "tilt docker build -- --target node-export -f Dockerfile.proto -o type=local,dest=. .",
     env = {"DOCKER_BUILDKIT": "1"},
@@ -273,6 +273,13 @@ if pyth:
         ignore = ["./solana/*/target"],
     )
 
+    # Automatic pyth2wormhole relay, showcasing p2w-sdk
+    docker_build(
+        ref = "p2w-relay",
+	context = ".",
+	dockerfile = "./third_party/pyth/p2w-relay/Dockerfile",
+    )
+
     k8s_yaml_with_ns("devnet/p2w-attest.yaml")
     k8s_resource(
         "p2w-attest",
@@ -282,6 +289,13 @@ if pyth:
         trigger_mode = trigger_mode,
     )
 
+    k8s_yaml_with_ns("devnet/p2w-relay.yaml")
+    k8s_resource(
+        "p2w-relay",
+        resource_deps = ["solana-devnet", "eth-devnet", "pyth", "guardian", "p2w-attest", "proto-gen-web", "wasm-gen"],
+        port_forwards = [],
+    )
+
 k8s_yaml_with_ns("devnet/eth-devnet.yaml")
 
 k8s_resource(

+ 3 - 0
devnet/p2w-attest.yaml

@@ -37,6 +37,9 @@ spec:
           command:
             - python3
             - /usr/src/pyth/p2w_autoattest.py
+          env:
+          - name: P2W_INITIALIZE_SOL_CONTRACT
+            value: "1"
           tty: true
           readinessProbe:
             tcpSocket:

+ 3 - 2
devnet/p2w-relay.yaml

@@ -31,8 +31,9 @@ spec:
         - name: p2w-relay
           image: p2w-relay
           command:
-            - node
-            - /usr/src/third_party/pyth/p2w-sdk/lib/autorelayer.js
+            - npm
+            - start
+          workingDir: /usr/src/third_party/pyth/p2w-relay/
           tty: true
           readinessProbe:
             tcpSocket:

+ 2 - 2
ethereum/.env.template

@@ -21,5 +21,5 @@ BRIDGE_INIT_WETH=           # 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
 PYTH_INIT_CHAIN_ID=         # 0x2
 PYTH_INIT_GOV_CHAIN_ID=     # 0x3
 PYTH_INIT_GOV_CONTRACT=     # 0x0000000000000000000000000000000000000000000000000000000000000004
-PYTH_TO_WORMHOLE_CHAIN_ID=  # 0x5
-PYTH_TO_WORMHOLE_CONTRACT=  # 0x0000000000000000000000000000000000000000000000000000000000000006
+PYTH_TO_WORMHOLE_CHAIN_ID=  # 0x1
+PYTH_TO_WORMHOLE_EMITTER=  # 8fuAZUxHecYLMC76ZNjYzwRybUiDv9LhkRQsAccEykLr

+ 2 - 2
ethereum/.env.test

@@ -14,5 +14,5 @@ BRIDGE_INIT_WETH=0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E
 PYTH_INIT_CHAIN_ID=0x2
 PYTH_INIT_GOV_CHAIN_ID=0x3
 PYTH_INIT_GOV_CONTRACT=0x0000000000000000000000000000000000000000000000000000000000000004
-PYTH_TO_WORMHOLE_CHAIN_ID=0x5
-PYTH_TO_WORMHOLE_CONTRACT=0x0000000000000000000000000000000000000000000000000000000000000006
+PYTH_TO_WORMHOLE_CHAIN_ID=0x1
+PYTH_TO_WORMHOLE_EMITTER=8fuAZUxHecYLMC76ZNjYzwRybUiDv9LhkRQsAccEykLr

+ 1 - 1
ethereum/Dockerfile

@@ -24,6 +24,6 @@ RUN --mount=type=cache,uid=1000,gid=1000,target=/home/node/.npm \
 # Amusingly, Debian's coreutils version has a bug where mv believes that
 # the target is on a different fs and does a full recursive copy for what
 # could be a renameat syscall. Alpine does not have this bug.
-RUN rmdir node_modules && mv node_modules_cache node_modules
+RUN rm -rf node_modules && mv node_modules_cache node_modules
 
 ADD --chown=node:node . .

+ 1 - 1
ethereum/contracts/pyth/Pyth.sol

@@ -34,7 +34,7 @@ contract Pyth is PythGovernance {
         if (vm.emitterChainId != pyth2WormholeChainId()) {
             return false;
         }
-        if (vm.emitterAddress != pyth2WormholeContract()) {
+        if (vm.emitterAddress != pyth2WormholeEmitter()) {
             return false;
         }
         return true;

+ 3 - 3
ethereum/contracts/pyth/PythGetters.sol

@@ -36,11 +36,11 @@ contract PythGetters is PythState {
         return _state.provider.pyth2WormholeChainId;
     }
 
-    function pyth2WormholeContract() public view returns (bytes32){
-        return _state.provider.pyth2WormholeContract;
+    function pyth2WormholeEmitter() public view returns (bytes32){
+        return _state.provider.pyth2WormholeEmitter;
     }
 
     function latestAttestation(bytes32 product, uint8 priceType) public view returns (PythStructs.PriceAttestation memory attestation){
         return _state.latestAttestations[product][priceType];
     }
-}
+}

+ 3 - 3
ethereum/contracts/pyth/PythSetters.sol

@@ -30,8 +30,8 @@ contract PythSetters is PythState {
         _state.provider.pyth2WormholeChainId = chainId;
     }
 
-    function setPyth2WormholeContract(bytes32 contractAddr) internal {
-        _state.provider.pyth2WormholeContract = contractAddr;
+    function setPyth2WormholeEmitter(bytes32 emitterAddr) internal {
+        _state.provider.pyth2WormholeEmitter = emitterAddr;
     }
 
     function setWormhole(address wh) internal {
@@ -41,4 +41,4 @@ contract PythSetters is PythState {
     function setLatestAttestation(bytes32 product, uint8 priceType, PythStructs.PriceAttestation memory attestation) internal {
         _state.latestAttestations[product][priceType] = attestation;
     }
-}
+}

+ 2 - 2
ethereum/contracts/pyth/PythSetup.sol

@@ -19,7 +19,7 @@ contract PythSetup is PythSetters, ERC1967Upgrade {
         bytes32 governanceContract,
 
         uint16 pyth2WormholeChainId,
-        bytes32 pyth2WormholeContract
+        bytes32 pyth2WormholeEmitter
     ) public {
         setChainId(chainId);
 
@@ -29,7 +29,7 @@ contract PythSetup is PythSetters, ERC1967Upgrade {
         setGovernanceContract(governanceContract);
 
         setPyth2WormholeChainId(pyth2WormholeChainId);
-        setPyth2WormholeContract(pyth2WormholeContract);
+        setPyth2WormholeEmitter(pyth2WormholeEmitter);
 
         _upgradeTo(implementation);
     }

+ 2 - 2
ethereum/contracts/pyth/PythState.sol

@@ -13,7 +13,7 @@ contract PythStorage {
         bytes32 governanceContract;
 
         uint16 pyth2WormholeChainId;
-        bytes32 pyth2WormholeContract;
+        bytes32 pyth2WormholeEmitter;
     }
 
     struct State {
@@ -35,4 +35,4 @@ contract PythStorage {
 
 contract PythState {
     PythStorage.State _state;
-}
+}

+ 1 - 0
ethereum/devnet_mnemonic.txt

@@ -0,0 +1 @@
+myth like bonus scare over problem client lizard pioneer submit female collect

+ 5 - 2
ethereum/migrations/5_deploy_pyth.js

@@ -1,4 +1,5 @@
 require('dotenv').config({ path: "../.env" });
+const bs58 = require("bs58");
 
 const PythDataBridge = artifacts.require("PythDataBridge");
 const PythImplementation = artifacts.require("PythImplementation");
@@ -9,7 +10,9 @@ const chainId = process.env.PYTH_INIT_CHAIN_ID;
 const governanceChainId = process.env.PYTH_INIT_GOV_CHAIN_ID;
 const governanceContract = process.env.PYTH_INIT_GOV_CONTRACT; // bytes32
 const pyth2WormholeChainId = process.env.PYTH_TO_WORMHOLE_CHAIN_ID;
-const pyth2WormholeContract = process.env.PYTH_TO_WORMHOLE_CONTRACT; // bytes32
+const pyth2WormholeEmitter = bs58.decode(process.env.PYTH_TO_WORMHOLE_EMITTER); // base58, must fit into bytes32
+
+console.log("Deploying Pyth with emitter", pyth2WormholeEmitter.toString("hex"))
 
 module.exports = async function (deployer) {
     // deploy implementation
@@ -29,7 +32,7 @@ module.exports = async function (deployer) {
         governanceContract,
 
         pyth2WormholeChainId,
-        pyth2WormholeContract,
+        "0x" + pyth2WormholeEmitter.toString("hex"),
     ).encodeABI();
 
     // deploy proxy

+ 5 - 5
ethereum/test/pyth.js

@@ -20,7 +20,7 @@ contract("Pyth", function () {
     const testGovernanceChainId = "3";
     const testGovernanceContract = "0x0000000000000000000000000000000000000000000000000000000000000004";
     const testPyth2WormholeChainId = "5";
-    const testPyth2WormholeContract = "0x0000000000000000000000000000000000000000000000000000000000000006";
+    const testPyth2WormholeEmitter = "0x0000000000000000000000000000000000000000000000000000000000000006";
 
 
     it("should be initialized with the correct signers and values", async function(){
@@ -39,8 +39,8 @@ contract("Pyth", function () {
         // pyth2wormhole
         const pyth2wormChain = await initialized.methods.pyth2WormholeChainId().call();
         assert.equal(pyth2wormChain, testPyth2WormholeChainId);
-        const pyth2wormContract = await initialized.methods.pyth2WormholeContract().call();
-        assert.equal(pyth2wormContract, testPyth2WormholeContract);
+        const pyth2wormEmitter = await initialized.methods.pyth2WormholeEmitter().call();
+        assert.equal(pyth2wormEmitter, testPyth2WormholeEmitter);
     })
 
     it("should accept a valid upgrade", async function() {
@@ -132,7 +132,7 @@ contract("Pyth", function () {
             1,
             1,
             testPyth2WormholeChainId,
-            testPyth2WormholeContract,
+            testPyth2WormholeEmitter,
             0,
             testUpdate,
             [
@@ -237,4 +237,4 @@ function zeroPadBytes(value, length) {
         value = "0" + value;
     }
     return value;
-}
+}

+ 6 - 1
solana/bridge/program/src/api/governance.rs

@@ -1,7 +1,9 @@
 use solitaire::*;
 
 use solana_program::{
+    log::sol_log,
     program::invoke_signed,
+    program_error::ProgramError,
     pubkey::Pubkey,
     sysvar::{
         clock::Clock,
@@ -40,7 +42,10 @@ fn verify_governance<'a, T>(vaa: &ClaimableVAA<'a, T>) -> Result<()>
 where
     T: DeserializePayload,
 {
-    let expected_emitter = std::env!("EMITTER_ADDRESS");
+    let expected_emitter = std::option_env!("EMITTER_ADDRESS").ok_or_else(|| {
+        sol_log("EMITTER_ADDRESS not set at compile-time");
+        ProgramError::UninitializedAccount
+    })?;
     let current_emitter = format!(
         "{}",
         Pubkey::new_from_array(vaa.message.meta().emitter_address)

+ 2 - 0
solana/pyth2wormhole/Cargo.lock

@@ -1785,6 +1785,8 @@ dependencies = [
  "env_logger 0.8.4",
  "log",
  "pyth2wormhole",
+ "serde",
+ "serde_yaml",
  "shellexpand",
  "solana-client",
  "solana-program",

+ 2 - 0
solana/pyth2wormhole/client/Cargo.toml

@@ -15,6 +15,8 @@ env_logger = "0.8.4"
 log = "0.4.14"
 wormhole-bridge-solana = {path = "../../bridge/program"}
 pyth2wormhole = {path = "../program"}
+serde = "1"
+serde_yaml = "0.8"
 shellexpand = "2.1.0"
 solana-client = "=1.9.4"
 solana-program = "=1.9.4"

+ 84 - 0
solana/pyth2wormhole/client/src/attestation_cfg.rs

@@ -0,0 +1,84 @@
+use std::str::FromStr;
+
+use serde::{
+    de::Error,
+    Deserialize,
+    Deserializer,
+    Serialize,
+    Serializer,
+};
+use solana_program::pubkey::Pubkey;
+use solitaire::ErrBox;
+
+/// Pyth2wormhole config specific to attestation requests
+#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
+pub struct AttestationConfig {
+    pub symbols: Vec<P2WSymbol>,
+}
+
+/// Config entry for a Pyth product + price pair
+#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
+pub struct P2WSymbol {
+    /// User-defined human-readable name
+    pub name: Option<String>,
+
+    #[serde(
+        deserialize_with = "pubkey_string_de",
+        serialize_with = "pubkey_string_ser"
+    )]
+    pub product_addr: Pubkey,
+    #[serde(
+        deserialize_with = "pubkey_string_de",
+        serialize_with = "pubkey_string_ser"
+    )]
+    pub price_addr: Pubkey,
+}
+
+// Helper methods for strinigified SOL addresses
+
+fn pubkey_string_ser<S>(k: &Pubkey, ser: S) -> Result<S::Ok, S::Error>
+where
+    S: Serializer,
+{
+    ser.serialize_str(&k.to_string())
+}
+
+fn pubkey_string_de<'de, D>(de: D) -> Result<Pubkey, D::Error>
+where
+    D: Deserializer<'de>,
+{
+    let pubkey_string = String::deserialize(de)?;
+    let pubkey = Pubkey::from_str(&pubkey_string).map_err(D::Error::custom)?;
+    Ok(pubkey)
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_sanity() -> Result<(), ErrBox> {
+        let initial = AttestationConfig {
+            symbols: vec![
+                P2WSymbol {
+                    name: Some("ETH/USD".to_owned()),
+                    product_addr: Default::default(),
+                    price_addr: Default::default(),
+                },
+                P2WSymbol {
+                    name: None,
+                    product_addr: Pubkey::new(&[42u8; 32]),
+                    price_addr: Default::default(),
+                },
+            ],
+        };
+
+        let serialized = serde_yaml::to_string(&initial)?;
+        eprintln!("Serialized:\n{}", serialized);
+
+        let deserialized: AttestationConfig = serde_yaml::from_str(&serialized)?;
+
+        assert_eq!(initial, deserialized);
+        Ok(())
+    }
+}

+ 7 - 10
solana/pyth2wormhole/client/src/cli.rs

@@ -1,6 +1,7 @@
 //! CLI options
 
 use solana_program::pubkey::Pubkey;
+use std::path::PathBuf;
 
 use clap::Clap;
 #[derive(Clap)]
@@ -22,7 +23,7 @@ pub struct Cli {
         default_value = "~/.config/solana/id.json"
     )]
     pub payer: String,
-    #[clap(long, default_value = "http://localhost:8899")]
+    #[clap(short, long, default_value = "http://localhost:8899")]
     pub rpc_url: String,
     #[clap(long)]
     pub p2w_addr: Pubkey,
@@ -35,23 +36,19 @@ pub enum Action {
     #[clap(about = "Initialize a pyth2wormhole program freshly deployed under <p2w_addr>")]
     Init {
 	/// The bridge program account
-	#[clap(long = "wh-prog")]
+	#[clap(short = 'w', long = "wh-prog")]
 	wh_prog: Pubkey,
-	#[clap(long = "owner")]
+	#[clap(short = 'o', long = "owner")]
         owner_addr: Pubkey,
-        #[clap(long = "pyth-owner")]
+        #[clap(short = 'p', long = "pyth-owner")]
         pyth_owner_addr: Pubkey,
     },
     #[clap(
         about = "Use an existing pyth2wormhole program to attest product price information to another chain"
     )]
     Attest {
-        #[clap(long = "product")]
-        product_addr: Pubkey,
-        #[clap(long = "price")]
-        price_addr: Pubkey,
-        #[clap(long)]
-	nonce: u32,
+	#[clap(short = 'f', long = "--config", about = "Attestation YAML config")]
+	attestation_cfg: PathBuf,
     },
     #[clap(about = "Update an existing pyth2wormhole program's settings (currently set owner only)")]
     SetConfig {

+ 38 - 0
solana/pyth2wormhole/client/src/config_file.rs

@@ -0,0 +1,38 @@
+#[derive(Deserialize, Serialize)]
+pub struct Config {
+    symbols: Vec<P2WSymbol>,
+}
+
+/// Config entry for a Pyth2Wormhole product + price pair
+#[derive(Deserialize, Serialize)]
+pub struct P2WSymbol {
+    /// Optional human-readable name, never used on-chain; makes
+    /// attester logs and the config easier to understand
+    name: Option<String>,
+    product: Pubkey,
+    price: Pubkey,
+}
+
+#[testmod]
+mod tests {
+    #[test]
+    fn test_sanity() -> Result<(), ErrBox> {
+        let serialized = r#"
+symbols:
+  - name: ETH/USD
+    product_addr: 11111111111111111111111111111111
+    price_addr: 11111111111111111111111111111111
+  - name: SOL/EUR
+    product_addr: 4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi
+    price_addr: 4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi
+  - name: BTC/CNY
+    product_addr: 8qbHbw2BbbTHBW1sbeqakYXVKRQM8Ne7pLK7m6CVfeR
+    price_addr: 8qbHbw2BbbTHBW1sbeqakYXVKRQM8Ne7pLK7m6CVfeR
+  - # no name
+    product_addr: 8qbHbw2BbbTHBW1sbeqakYXVKRQM8Ne7pLK7m6CVfeR
+    price_addr: 8qbHbw2BbbTHBW1sbeqakYXVKRQM8Ne7pLK7m6CVfeR
+"#;
+        let deserialized = serde_yaml::from_str(serialized)?;
+        Ok(())
+    }
+}

+ 221 - 113
solana/pyth2wormhole/client/src/main.rs

@@ -1,11 +1,23 @@
+pub mod attestation_cfg;
 pub mod cli;
 
+use std::{
+    fs::File,
+    path::{
+        Path,
+        PathBuf,
+    },
+};
+
 use borsh::{
     BorshDeserialize,
     BorshSerialize,
 };
 use clap::Clap;
 use log::{
+    debug,
+    error,
+    info,
     warn,
     LevelFilter,
 };
@@ -59,7 +71,10 @@ use bridge::{
 };
 
 use pyth2wormhole::{
-    attest::P2WEmitter,
+    attest::{
+        P2WEmitter,
+        P2W_MAX_BATCH_SIZE,
+    },
     config::P2WConfigAccount,
     initialize::InitializeAccounts,
     set_config::SetConfigAccounts,
@@ -68,6 +83,8 @@ use pyth2wormhole::{
     Pyth2WormholeConfig,
 };
 
+use crate::attestation_cfg::AttestationConfig;
+
 pub type ErrBox = Box<dyn std::error::Error>;
 
 pub const SEQNO_PREFIX: &'static str = "Program log: Sequence: ";
@@ -81,65 +98,49 @@ fn main() -> Result<(), ErrBox> {
 
     let p2w_addr = cli.p2w_addr;
 
-    let (recent_blockhash, _) = rpc_client.get_recent_blockhash()?;
+    let latest_blockhash = rpc_client.get_latest_blockhash()?;
 
-    let tx = match cli.action {
+    match cli.action {
         Action::Init {
             owner_addr,
             pyth_owner_addr,
             wh_prog,
-        } => handle_init(
-            payer,
-            p2w_addr,
-            owner_addr,
-            wh_prog,
-            pyth_owner_addr,
-            recent_blockhash,
-        )?,
+        } => {
+            let tx = handle_init(
+                payer,
+                p2w_addr,
+                owner_addr,
+                wh_prog,
+                pyth_owner_addr,
+                latest_blockhash,
+            )?;
+            rpc_client.send_and_confirm_transaction_with_spinner(&tx)?;
+        }
         Action::SetConfig {
             ref owner,
             new_owner_addr,
             new_wh_prog,
             new_pyth_owner_addr,
-        } => handle_set_config(
-            payer,
-            p2w_addr,
-            read_keypair_file(&*shellexpand::tilde(&owner))?,
-            new_owner_addr,
-            new_wh_prog,
-            new_pyth_owner_addr,
-            recent_blockhash,
-        )?,
+        } => {
+            let tx = handle_set_config(
+                payer,
+                p2w_addr,
+                read_keypair_file(&*shellexpand::tilde(&owner))?,
+                new_owner_addr,
+                new_wh_prog,
+                new_pyth_owner_addr,
+                latest_blockhash,
+            )?;
+            rpc_client.send_and_confirm_transaction_with_spinner(&tx)?;
+        }
         Action::Attest {
-            product_addr,
-            price_addr,
-            nonce,
-        } => handle_attest(
-            &rpc_client,
-            payer,
-            p2w_addr,
-            product_addr,
-            price_addr,
-            nonce,
-            recent_blockhash,
-        )?,
-    };
+            ref attestation_cfg,
+        } => {
+            // Load the attestation config yaml
+            let attestation_cfg: AttestationConfig =
+                serde_yaml::from_reader(File::open(attestation_cfg)?)?;
 
-    let sig = rpc_client.send_and_confirm_transaction_with_spinner(&tx)?;
-
-    // To complete attestation, retrieve sequence number from transaction logs
-    if let Action::Attest { .. } = cli.action {
-        let this_tx = rpc_client.get_transaction(&sig, UiTransactionEncoding::Json)?;
-
-        if let Some(logs) = this_tx.transaction.meta.and_then(|meta| meta.log_messages) {
-	    for log in logs {
-		if log.starts_with(SEQNO_PREFIX) {
-		    let seqno = log.replace(SEQNO_PREFIX, "");
-		    println!("Sequence number: {}", seqno);
-		}
-	    }
-        } else {
-            warn!("Could not get program logs for attestation");
+            handle_attest(&rpc_client, payer, p2w_addr, &attestation_cfg)?;
         }
     }
 
@@ -152,7 +153,7 @@ fn handle_init(
     new_owner_addr: Pubkey,
     wh_prog: Pubkey,
     pyth_owner_addr: Pubkey,
-    recent_blockhash: Hash,
+    latest_blockhash: Hash,
 ) -> Result<Transaction, ErrBox> {
     use AccEntry::*;
 
@@ -164,6 +165,7 @@ fn handle_init(
     };
 
     let config = Pyth2WormholeConfig {
+        max_batch_size: P2W_MAX_BATCH_SIZE,
         owner: new_owner_addr,
         wh_prog: wh_prog,
         pyth_owner: pyth_owner_addr,
@@ -176,7 +178,7 @@ fn handle_init(
         &[ix],
         Some(&payer_pubkey),
         signers.iter().collect::<Vec<_>>().as_ref(),
-        recent_blockhash,
+        latest_blockhash,
     );
     Ok(tx_signed)
 }
@@ -188,14 +190,12 @@ fn handle_set_config(
     new_owner_addr: Pubkey,
     new_wh_prog: Pubkey,
     new_pyth_owner_addr: Pubkey,
-    recent_blockhash: Hash,
+    latest_blockhash: Hash,
 ) -> Result<Transaction, ErrBox> {
     use AccEntry::*;
 
     let payer_pubkey = payer.pubkey();
 
-    println!("Canary!");
-
     let accs = SetConfigAccounts {
         payer: Signer(payer),
         current_owner: Signer(owner),
@@ -203,6 +203,7 @@ fn handle_set_config(
     };
 
     let config = Pyth2WormholeConfig {
+        max_batch_size: P2W_MAX_BATCH_SIZE,
         owner: new_owner_addr,
         wh_prog: new_wh_prog,
         pyth_owner: new_pyth_owner_addr,
@@ -215,30 +216,28 @@ fn handle_set_config(
         &[ix],
         Some(&payer_pubkey),
         signers.iter().collect::<Vec<_>>().as_ref(),
-        recent_blockhash,
+        latest_blockhash,
     );
     Ok(tx_signed)
 }
 
 fn handle_attest(
-    rpc: &RpcClient, // Needed for reading Pyth account data
+    rpc_client: &RpcClient, // Needed for reading Pyth account data
     payer: Keypair,
     p2w_addr: Pubkey,
-    product_addr: Pubkey,
-    price_addr: Pubkey,
-    nonce: u32,
-    recent_blockhash: Hash,
-) -> Result<Transaction, ErrBox> {
-    let message_keypair = Keypair::new();
-
+    attestation_cfg: &AttestationConfig,
+) -> Result<(), ErrBox> {
+    // Derive seeded accounts
     let emitter_addr = P2WEmitter::key(None, &p2w_addr);
 
+    info!("Using emitter addr {}", emitter_addr);
+
     let p2w_config_addr = P2WConfigAccount::<{ AccountState::Initialized }>::key(None, &p2w_addr);
 
-    let config =
-        Pyth2WormholeConfig::try_from_slice(rpc.get_account_data(&p2w_config_addr)?.as_slice())?;
+    let config = Pyth2WormholeConfig::try_from_slice(
+        rpc_client.get_account_data(&p2w_config_addr)?.as_slice(),
+    )?;
 
-    // Derive dynamic seeded accounts
     let seq_addr = Sequence::key(
         &SequenceDerivationData {
             emitter_key: &emitter_addr,
@@ -246,58 +245,167 @@ fn handle_attest(
         &config.wh_prog,
     );
 
-    // Arrange Attest accounts
-    let acc_metas = vec![
-        // payer
-        AccountMeta::new(payer.pubkey(), true),
-        // system_program
-        AccountMeta::new_readonly(system_program::id(), false),
-        // config
-        AccountMeta::new_readonly(p2w_config_addr, false),
-        // pyth_product
-        AccountMeta::new_readonly(product_addr, false),
-        // pyth_price
-        AccountMeta::new_readonly(price_addr, false),
-        // clock
-        AccountMeta::new_readonly(clock::id(), false),
-        // wh_prog
-        AccountMeta::new_readonly(config.wh_prog, false),
-        // wh_bridge
-        AccountMeta::new(
-            Bridge::<{ AccountState::Initialized }>::key(None, &config.wh_prog),
-            false,
-        ),
-        // wh_message
-        AccountMeta::new(message_keypair.pubkey(), true),
-        // wh_emitter
-        AccountMeta::new_readonly(emitter_addr, false),
-        // wh_sequence
-        AccountMeta::new(seq_addr, false),
-        // wh_fee_collector
-        AccountMeta::new(FeeCollector::<'_>::key(None, &config.wh_prog), false),
-        AccountMeta::new_readonly(rent::id(), false),
-    ];
-
-    let ix_data = (
-        pyth2wormhole::instruction::Instruction::Attest,
-        AttestData {
-            nonce,
-            consistency_level: ConsistencyLevel::Finalized,
-        },
-    );
+    // Read the current max batch size from the contract's settings
+    let max_batch_size = config.max_batch_size;
 
-    let ix = Instruction::new_with_bytes(p2w_addr, ix_data.try_to_vec()?.as_slice(), acc_metas);
+    let batch_count = {
+        let whole_batches = attestation_cfg.symbols.len() / config.max_batch_size as usize;
 
-    // Signers that use off-chain keypairs
-    let signer_keypairs = vec![&payer, &message_keypair];
+        // Include  partial batch if there is a remainder
+        if attestation_cfg.symbols.len() % config.max_batch_size as usize > 0 {
+            whole_batches + 1
+        } else {
+            whole_batches
+        }
+    };
 
-    let tx_signed = Transaction::new_signed_with_payer::<Vec<&Keypair>>(
-        &[ix],
-        Some(&payer.pubkey()),
-        &signer_keypairs,
-        recent_blockhash,
+    debug!("Symbol config:\n{:#?}", attestation_cfg);
+
+    info!(
+        "{} symbols read, max batch size {}, dividing into {} batches",
+        attestation_cfg.symbols.len(),
+        max_batch_size,
+        batch_count
     );
-    Ok(tx_signed)
+
+    for (idx, symbols) in attestation_cfg
+        .symbols
+        .as_slice()
+        .chunks(max_batch_size as usize)
+        .enumerate()
+    {
+        let batch_no = idx + 1;
+        let sym_msg_keypair = Keypair::new();
+        info!(
+            "Batch {}/{} contents: {:?}",
+            batch_no,
+            batch_count,
+            symbols
+                .iter()
+                .map(|s| s
+                    .name
+                    .clone()
+                    .unwrap_or(format!("unnamed product {:?}", s.product_addr)))
+                .collect::<Vec<_>>()
+        );
+
+        let mut sym_metas_vec: Vec<_> = symbols
+            .iter()
+            .map(|s| {
+                vec![
+                    AccountMeta::new_readonly(s.product_addr, false),
+                    AccountMeta::new_readonly(s.price_addr, false),
+                ]
+            })
+            .flatten()
+            .collect();
+
+        // Align to max batch size with null accounts
+        let mut blank_accounts =
+            vec![
+                AccountMeta::new_readonly(Pubkey::new_from_array([0u8; 32]), false);
+                2 * (max_batch_size as usize - symbols.len())
+            ];
+        sym_metas_vec.append(&mut blank_accounts);
+
+        // Arrange Attest accounts
+        let mut acc_metas = vec![
+            // payer
+            AccountMeta::new(payer.pubkey(), true),
+            // system_program
+            AccountMeta::new_readonly(system_program::id(), false),
+            // config
+            AccountMeta::new_readonly(p2w_config_addr, false),
+        ];
+
+        // Insert max_batch_size metas
+        acc_metas.append(&mut sym_metas_vec);
+
+        // Continue with other pyth2wormhole accounts
+        let mut acc_metas_remainder = vec![
+            // clock
+            AccountMeta::new_readonly(clock::id(), false),
+            // wh_prog
+            AccountMeta::new_readonly(config.wh_prog, false),
+            // wh_bridge
+            AccountMeta::new(
+                Bridge::<{ AccountState::Initialized }>::key(None, &config.wh_prog),
+                false,
+            ),
+            // wh_message
+            AccountMeta::new(sym_msg_keypair.pubkey(), true),
+            // wh_emitter
+            AccountMeta::new_readonly(emitter_addr, false),
+            // wh_sequence
+            AccountMeta::new(seq_addr, false),
+            // wh_fee_collector
+            AccountMeta::new(FeeCollector::<'_>::key(None, &config.wh_prog), false),
+            AccountMeta::new_readonly(rent::id(), false),
+        ];
+
+        acc_metas.append(&mut acc_metas_remainder);
+
+        let ix_data = (
+            pyth2wormhole::instruction::Instruction::Attest,
+            AttestData {
+                consistency_level: ConsistencyLevel::Finalized,
+            },
+        );
+
+        let ix = Instruction::new_with_bytes(p2w_addr, ix_data.try_to_vec()?.as_slice(), acc_metas);
+
+        // Execute the transaction, obtain the resulting sequence
+        // number. The and_then() calls enforce error handling
+        // location near loop end.
+        let res = rpc_client
+            .get_latest_blockhash()
+            .and_then(|latest_blockhash| {
+                let tx_signed = Transaction::new_signed_with_payer::<Vec<&Keypair>>(
+                    &[ix],
+                    Some(&payer.pubkey()),
+                    &vec![&payer, &sym_msg_keypair],
+                    latest_blockhash,
+                );
+                rpc_client.send_and_confirm_transaction_with_spinner(&tx_signed)
+            })
+            .and_then(|sig| rpc_client.get_transaction(&sig, UiTransactionEncoding::Json))
+            .map_err(|e| -> ErrBox { e.into() })
+            .and_then(|this_tx| {
+                this_tx
+                    .transaction
+                    .meta
+                    .and_then(|meta| meta.log_messages)
+                    .and_then(|logs| {
+                        let mut seqno = None;
+                        for log in logs {
+                            if log.starts_with(SEQNO_PREFIX) {
+                                seqno = Some(log.replace(SEQNO_PREFIX, ""));
+                                break;
+                            }
+                        }
+                        seqno
+                    })
+                    .ok_or_else(|| format!("No seqno in program logs").into())
+            });
+
+        // Individual batch errors mustn't prevent other batches from being sent.
+        match res {
+            Ok(seqno) => {
+                println!("Sequence number: {}", seqno);
+                info!("Batch {}/{}: OK, seqno {}", batch_no, batch_count, seqno);
+            }
+            Err(e) => {
+                error!(
+                    "Batch {}/{} tx error: {}",
+                    batch_no,
+                    batch_count,
+                    e.to_string()
+                );
+            }
+        }
+    }
+
+    Ok(())
 }
 
 fn init_logging(verbosity: u32) {

+ 135 - 26
solana/pyth2wormhole/program/src/attest.rs

@@ -1,6 +1,6 @@
 use crate::{
     config::P2WConfigAccount,
-    types::PriceAttestation,
+    types::{PriceAttestation, batch_serialize},
 };
 use borsh::{
     BorshDeserialize,
@@ -28,6 +28,7 @@ use bridge::{
 };
 
 use solitaire::{
+    invoke_seeded,
     trace,
     AccountState,
     Derive,
@@ -40,7 +41,6 @@ use solitaire::{
     Peel,
     Result as SoliResult,
     Seeded,
-    invoke_seeded,
     Signer,
     SolitaireError,
     Sysvar,
@@ -49,20 +49,66 @@ use solitaire::{
 
 pub type P2WEmitter<'b> = Derive<Info<'b>, "p2w-emitter">;
 
+/// Important: must be manually maintained until native Solitaire
+/// variable len vector support.
+///
+/// The number must reflect how many pyth product/price pairs are
+/// expected in the Attest struct below. The constant itself is only
+/// used in the on-chain config in order for attesters to learn the
+/// correct value dynamically.
+pub const P2W_MAX_BATCH_SIZE: u16 = 5;
+
 #[derive(FromAccounts, ToInstruction)]
 pub struct Attest<'b> {
     // Payer also used for wormhole
     pub payer: Mut<Signer<Info<'b>>>,
     pub system_program: Info<'b>,
     pub config: P2WConfigAccount<'b, { AccountState::Initialized }>,
+
+    // Hardcoded product/price pairs, bypassing Solitaire's variable-length limitations
+    // Any change to the number of accounts must include an appropriate change to P2W_MAX_BATCH_SIZE
     pub pyth_product: Info<'b>,
     pub pyth_price: Info<'b>,
+
+    pub pyth_product2: Option<Info<'b>>,
+    pub pyth_price2: Option<Info<'b>>,
+
+    pub pyth_product3: Option<Info<'b>>,
+    pub pyth_price3: Option<Info<'b>>,
+
+    pub pyth_product4: Option<Info<'b>>,
+    pub pyth_price4: Option<Info<'b>>,
+
+    pub pyth_product5: Option<Info<'b>>,
+    pub pyth_price5: Option<Info<'b>>,
+
+    // Did you read the comment near `pyth_product`?
+    // pub pyth_product6: Option<Info<'b>>,
+    // pub pyth_price6: Option<Info<'b>>,
+
+    // pub pyth_product7: Option<Info<'b>>,
+    // pub pyth_price7: Option<Info<'b>>,
+
+    // pub pyth_product8: Option<Info<'b>>,
+    // pub pyth_price8: Option<Info<'b>>,
+
+    // pub pyth_product9: Option<Info<'b>>,
+    // pub pyth_price9: Option<Info<'b>>,
+
+    // pub pyth_product10: Option<Info<'b>>,
+    // pub pyth_price10: Option<Info<'b>>,
+
     pub clock: Sysvar<'b, Clock>,
 
-    // post_message accounts
-    /// Wormhole program address
+    /// Wormhole program address - must match the config value
     pub wh_prog: Info<'b>,
 
+    // wormhole's post_message accounts
+    //
+    // This contract makes no attempt to exhaustively validate
+    // Wormhole's account inputs. Only the wormhole contract address
+    // is validated (see above).
+
     /// Bridge config needed for fee calculation
     pub wh_bridge: Mut<Info<'b>>,
 
@@ -85,7 +131,6 @@ pub struct Attest<'b> {
 
 #[derive(BorshDeserialize, BorshSerialize)]
 pub struct AttestData {
-    pub nonce: u32,
     pub consistency_level: ConsistencyLevel,
 }
 
@@ -98,16 +143,6 @@ impl<'b> InstructionContext<'b> for Attest<'b> {
 pub fn attest(ctx: &ExecutionContext, accs: &mut Attest, data: AttestData) -> SoliResult<()> {
     accs.config.verify_derivation(ctx.program_id, None)?;
 
-    if accs.config.pyth_owner != *accs.pyth_price.owner
-        || accs.config.pyth_owner != *accs.pyth_product.owner
-    {
-        trace!(&format!(
-            "pyth_owner pubkey mismatch (expected {:?}, got price owner {:?} and product owner {:?}",
-            accs.config.pyth_owner, accs.pyth_price.owner, accs.pyth_product.owner
-        ));
-        return Err(SolitaireError::InvalidOwner(accs.pyth_price.owner.clone()).into());
-    }
-
     if accs.config.wh_prog != *accs.wh_prog.key {
         trace!(&format!(
             "Wormhole program account mismatch (expected {:?}, got {:?})",
@@ -115,20 +150,91 @@ pub fn attest(ctx: &ExecutionContext, accs: &mut Attest, data: AttestData) -> So
         ));
     }
 
-    let price_attestation = PriceAttestation::from_pyth_price_bytes(
-        accs.pyth_price.key.clone(),
-        accs.clock.unix_timestamp,
-        &*accs.pyth_price.try_borrow_data()?,
-    )?;
+    // Make the specified prices iterable
+    let price_pair_opts = [
+        Some(&accs.pyth_product),
+        Some(&accs.pyth_price),
+        accs.pyth_product2.as_ref(),
+        accs.pyth_price2.as_ref(),
+        accs.pyth_product3.as_ref(),
+        accs.pyth_price3.as_ref(),
+        accs.pyth_product4.as_ref(),
+        accs.pyth_price4.as_ref(),
+        accs.pyth_product5.as_ref(),
+        accs.pyth_price5.as_ref(),
+
+	// Did you read the comment near `pyth_product`?
+	// accs.pyth_product6.as_ref(),
+        // accs.pyth_price6.as_ref(),
+        // accs.pyth_product7.as_ref(),
+        // accs.pyth_price7.as_ref(),
+        // accs.pyth_product8.as_ref(),
+        // accs.pyth_price8.as_ref(),
+        // accs.pyth_product9.as_ref(),
+        // accs.pyth_price9.as_ref(),
+        // accs.pyth_product10.as_ref(),
+        // accs.pyth_price10.as_ref(),
+    ];
 
-    if &price_attestation.product_id != accs.pyth_product.key {
+    let price_pairs: Vec<_> = price_pair_opts.into_iter().filter_map(|acc| *acc).collect();
+
+    if price_pairs.len() % 2 != 0 {
         trace!(&format!(
-            "Price's product_id does not match the pased account (points at {:?} instead)",
-            price_attestation.product_id
+            "Uneven product/price count detected: {}",
+            price_pairs.len()
         ));
         return Err(ProgramError::InvalidAccountData.into());
     }
 
+    trace!("{} Pyth symbols received", price_pairs.len() / 2);
+
+    // Collect the validated symbols for batch serialization
+    let mut attestations = Vec::with_capacity(price_pairs.len() / 2);
+
+    for pair in price_pairs.as_slice().chunks_exact(2) {
+
+	let product = pair[0];
+	let price = pair[1];
+
+        if accs.config.pyth_owner != *price.owner
+            || accs.config.pyth_owner != *product.owner
+        {
+            trace!(&format!(
+            "Pair {:?} - {:?}: pyth_owner pubkey mismatch (expected {:?}, got product owner {:?} and price owner {:?}",
+		
+		product, price, 
+            accs.config.pyth_owner, product.owner, price.owner
+        ));
+            return Err(SolitaireError::InvalidOwner(accs.pyth_price.owner.clone()).into());
+        }
+
+	let attestation = PriceAttestation::from_pyth_price_bytes(
+	    price.key.clone(),
+	    accs.clock.unix_timestamp,
+	    &*price.try_borrow_data()?,
+	)?;
+
+	// The following check is crucial against poorly ordered
+	// account inputs, e.g. [Some(prod1), Some(price1),
+	// Some(prod2), None, None, Some(price)], interpreted by
+	// earlier logic as [(prod1, price1), (prod2, price3)].
+	//
+	// Failing to verify the product/price relationship could lead
+	// to mismatched product/price metadata, which would result in
+	// a false attestation.
+	if &attestation.product_id != product.key {
+	    trace!(&format!(
+		"Price's product_id does not match the pased account (points at {:?} instead)",
+		attestation.product_id
+	    ));
+	    return Err(ProgramError::InvalidAccountData.into());
+	}
+
+	attestations.push(attestation);
+    }
+
+    trace!("Attestations successfully created");
+
     let bridge_config = BridgeData::try_from_slice(&accs.wh_bridge.try_borrow_mut_data()?)?.config;
 
     // Pay wormhole fee
@@ -143,9 +249,12 @@ pub fn attest(ctx: &ExecutionContext, accs: &mut Attest, data: AttestData) -> So
     let post_message_data = (
         bridge::instruction::Instruction::PostMessage,
         PostMessageData {
-            nonce: data.nonce,
-            payload: price_attestation.serialize(),
-            consistency_level: data.consistency_level,
+	    nonce: 0, // Superseded by the sequence number
+            payload: batch_serialize(attestations.as_slice().iter()).map_err(|e| {
+		trace!(e.to_string());
+		ProgramError::InvalidAccountData
+	    })?,
+	    consistency_level: data.consistency_level,
         },
     );
 

+ 13 - 0
solana/pyth2wormhole/program/src/config.rs

@@ -1,3 +1,10 @@
+//! On-chain state for the pyth2wormhole SOL contract.
+//!
+//! Important: A config init/update should be performed on every
+//! deployment/upgrade of this Solana program. Doing so prevents
+//! problems related to max batch size mismatches between config and
+//! contract logic. See attest.rs for details.
+
 use borsh::{BorshDeserialize, BorshSerialize};
 use solana_program::pubkey::Pubkey;
 use solitaire::{processors::seeded::AccountOwner, AccountState, Data, Derive, Owned};
@@ -10,6 +17,12 @@ pub struct Pyth2WormholeConfig {
     pub wh_prog: Pubkey,
     /// Authority owning Pyth price data
     pub pyth_owner: Pubkey,
+    /// How many product/price pairs can be sent and attested at once
+    ///
+    /// Important: Whenever the corresponding logic in attest.rs
+    /// changes its expected number of symbols per batch, this config
+    /// must be updated accordingly on-chain.
+    pub max_batch_size: u16,
 }
 
 impl Owned for Pyth2WormholeConfig {

+ 243 - 58
solana/pyth2wormhole/program/src/types/mod.rs

@@ -1,11 +1,21 @@
+//! Constants and values common to every p2w custom-serialized message.
+//!
+//! The format makes no attempt to provide human-readable symbol names
+//! in favor of explicit product/price Solana account addresses
+//! (IDs). This choice was made to disambiguate any symbols with
+//! similar human-readable names and provide a failsafe for some of
+//! the probable adversarial scenarios.
+
 pub mod pyth_extensions;
 
 use std::{
+    borrow::Borrow,
     convert::{
         TryFrom,
         TryInto,
     },
     io::Read,
+    iter::Iterator,
     mem,
 };
 
@@ -37,26 +47,34 @@ use self::pyth_extensions::{
     P2WPriceType,
 };
 
-// Constants and values common to every p2w custom-serialized message
 
 /// Precedes every message implementing the p2w serialization format
 pub const P2W_MAGIC: &'static [u8] = b"P2WH";
 
 /// Format version used and understood by this codebase
-pub const P2W_FORMAT_VERSION: u16 = 1;
+pub const P2W_FORMAT_VERSION: u16 = 2;
 
 pub const PUBKEY_LEN: usize = 32;
 
 /// Decides the format of following bytes
 #[repr(u8)]
 pub enum PayloadId {
-    PriceAttestation = 1,
+    PriceAttestation = 1, // Not in use, currently batch attestations imply PriceAttestation messages inside
+    PriceBatchAttestation,
 }
 
 // On-chain data types
 
+/// The main attestation data type.
+///
+/// Important: For maximum security, *both* product_id and price_id
+/// should be used as storage keys for known attestations in target
+/// chain logic.
 #[derive(Clone, Default, Debug, Eq, PartialEq)]
-#[cfg_attr(feature = "wasm", derive(serde_derive::Serialize, serde_derive::Deserialize))]
+#[cfg_attr(
+    feature = "wasm",
+    derive(serde_derive::Serialize, serde_derive::Deserialize)
+)]
 pub struct PriceAttestation {
     pub product_id: Pubkey,
     pub price_id: Pubkey,
@@ -71,6 +89,123 @@ pub struct PriceAttestation {
     pub timestamp: UnixTimestamp,
 }
 
+/// Turn a bunch of attestations into a combined payload.
+///
+/// Batches assume constant-size attestations within a single batch.
+pub fn batch_serialize(
+    attestations: impl Iterator<Item = impl Borrow<PriceAttestation>>,
+) -> Result<Vec<u8>, ErrBox> {
+    // magic
+    let mut buf = P2W_MAGIC.to_vec();
+
+    // version
+    buf.extend_from_slice(&P2W_FORMAT_VERSION.to_be_bytes()[..]);
+
+    // payload_id
+    buf.push(PayloadId::PriceBatchAttestation as u8);
+
+    let collected: Vec<_> = attestations.collect();
+
+    // n_attestations
+    buf.extend_from_slice(&(collected.len() as u16).to_be_bytes()[..]);
+
+    let mut attestation_size = 0; // Will be determined as we serialize attestations
+    let mut serialized_attestations = Vec::with_capacity(collected.len());
+    for (idx, a) in collected.iter().enumerate() {
+        // Learn the current attestation's size
+        let serialized = PriceAttestation::serialize(a.borrow());
+        let a_len = serialized.len();
+
+        // Verify it's the same as the first one we saw for the batch, assign if we're first.
+        if attestation_size > 0 {
+            if a_len != attestation_size {
+                return Err(format!(
+                    "attestation {} serializes to {} bytes, {} expected",
+                    idx + 1,
+                    a_len,
+                    attestation_size
+                )
+                .into());
+            }
+        } else {
+            attestation_size = a_len;
+        }
+
+        serialized_attestations.push(serialized);
+    }
+
+    // attestation_size
+    buf.extend_from_slice(&(attestation_size as u16).to_be_bytes()[..]);
+
+    for mut s in serialized_attestations.into_iter() {
+        buf.append(&mut s)
+    }
+
+    Ok(buf)
+}
+
+/// Undo `batch_serialize`
+pub fn batch_deserialize(mut bytes: impl Read) -> Result<Vec<PriceAttestation>, ErrBox> {
+    let mut magic_vec = vec![0u8; P2W_MAGIC.len()];
+    bytes.read_exact(magic_vec.as_mut_slice())?;
+
+    if magic_vec.as_slice() != P2W_MAGIC {
+        return Err(format!(
+            "Invalid magic {:02X?}, expected {:02X?}",
+            magic_vec, P2W_MAGIC,
+        )
+        .into());
+    }
+
+    let mut version_vec = vec![0u8; mem::size_of_val(&P2W_FORMAT_VERSION)];
+    bytes.read_exact(version_vec.as_mut_slice())?;
+    let version = u16::from_be_bytes(version_vec.as_slice().try_into()?);
+
+    if version != P2W_FORMAT_VERSION {
+        return Err(format!(
+            "Unsupported format version {}, expected {}",
+            version, P2W_FORMAT_VERSION
+        )
+        .into());
+    }
+
+    let mut payload_id_vec = vec![0u8; mem::size_of::<PayloadId>()];
+    bytes.read_exact(payload_id_vec.as_mut_slice())?;
+
+    if payload_id_vec[0] != PayloadId::PriceBatchAttestation as u8 {
+        return Err(format!(
+            "Invalid Payload ID {}, expected {}",
+            payload_id_vec[0],
+            PayloadId::PriceBatchAttestation as u8,
+        )
+        .into());
+    }
+
+    let mut batch_len_vec = vec![0u8; 2];
+    bytes.read_exact(batch_len_vec.as_mut_slice())?;
+    let batch_len = u16::from_be_bytes(batch_len_vec.as_slice().try_into()?);
+
+    let mut attestation_size_vec = vec![0u8; 2];
+    bytes.read_exact(attestation_size_vec.as_mut_slice())?;
+    let attestation_size = u16::from_be_bytes(attestation_size_vec.as_slice().try_into()?);
+
+    let mut ret = Vec::with_capacity(batch_len as usize);
+
+    for i in 0..batch_len {
+        let mut attestation_buf = vec![0u8; attestation_size as usize];
+        bytes.read_exact(attestation_buf.as_mut_slice())?;
+
+        dbg!(&attestation_buf.len());
+
+        match PriceAttestation::deserialize(attestation_buf.as_slice()) {
+            Ok(attestation) => ret.push(attestation),
+            Err(e) => return Err(format!("PriceAttestation {}/{}: {}", i + 1, batch_len, e).into()),
+        }
+    }
+
+    Ok(ret)
+}
+
 impl PriceAttestation {
     pub fn from_pyth_price_bytes(
         price_id: Pubkey,
@@ -161,7 +296,6 @@ impl PriceAttestation {
         use P2WPriceStatus::*;
         use P2WPriceType::*;
 
-	println!("Using {} bytes for magic", P2W_MAGIC.len());
         let mut magic_vec = vec![0u8; P2W_MAGIC.len()];
 
         bytes.read_exact(magic_vec.as_mut_slice())?;
@@ -176,7 +310,7 @@ impl PriceAttestation {
 
         let mut version_vec = vec![0u8; mem::size_of_val(&P2W_FORMAT_VERSION)];
         bytes.read_exact(version_vec.as_mut_slice())?;
-        let mut version = u16::from_be_bytes(version_vec.as_slice().try_into()?);
+        let version = u16::from_be_bytes(version_vec.as_slice().try_into()?);
 
         if version != P2W_FORMAT_VERSION {
             return Err(format!(
@@ -217,20 +351,21 @@ impl PriceAttestation {
         };
 
         let mut price_vec = vec![0u8; mem::size_of::<i64>()];
-	bytes.read_exact(price_vec.as_mut_slice())?;
-	let price = i64::from_be_bytes(price_vec.as_slice().try_into()?);
+        bytes.read_exact(price_vec.as_mut_slice())?;
+        let price = i64::from_be_bytes(price_vec.as_slice().try_into()?);
 
         let mut expo_vec = vec![0u8; mem::size_of::<i32>()];
-	bytes.read_exact(expo_vec.as_mut_slice())?;
-	let expo = i32::from_be_bytes(expo_vec.as_slice().try_into()?);
+        bytes.read_exact(expo_vec.as_mut_slice())?;
+        let expo = i32::from_be_bytes(expo_vec.as_slice().try_into()?);
 
-	let twap = P2WEma::deserialize(&mut bytes)?;
-	let twac = P2WEma::deserialize(&mut bytes)?;
+        let twap = P2WEma::deserialize(&mut bytes)?;
+        let twac = P2WEma::deserialize(&mut bytes)?;
 
-	println!("twac OK");
+        println!("twac OK");
         let mut confidence_interval_vec = vec![0u8; mem::size_of::<u64>()];
-	bytes.read_exact(confidence_interval_vec.as_mut_slice())?;
-	let confidence_interval = u64::from_be_bytes(confidence_interval_vec.as_slice().try_into()?);
+        bytes.read_exact(confidence_interval_vec.as_mut_slice())?;
+        let confidence_interval =
+            u64::from_be_bytes(confidence_interval_vec.as_slice().try_into()?);
 
         let mut status_vec = vec![0u8; mem::size_of::<P2WPriceType>()];
         bytes.read_exact(status_vec.as_mut_slice())?;
@@ -244,7 +379,6 @@ impl PriceAttestation {
             }
         };
 
-
         let mut corp_act_vec = vec![0u8; mem::size_of::<P2WPriceType>()];
         bytes.read_exact(corp_act_vec.as_mut_slice())?;
         let corp_act = match corp_act_vec[0] {
@@ -254,23 +388,23 @@ impl PriceAttestation {
             }
         };
 
-	let mut timestamp_vec = vec![0u8; mem::size_of::<UnixTimestamp>()];
-			 bytes.read_exact(timestamp_vec.as_mut_slice())?;
-	let timestamp = UnixTimestamp::from_be_bytes(timestamp_vec.as_slice().try_into()?);
-
-	Ok( Self {
-	    product_id,
-	    price_id,
-	    price_type,
-	    price,
-	    expo,
-	    twap,
-	    twac,
-	    confidence_interval,
-	    status,
-	    corp_act,
-	    timestamp
-	})
+        let mut timestamp_vec = vec![0u8; mem::size_of::<UnixTimestamp>()];
+        bytes.read_exact(timestamp_vec.as_mut_slice())?;
+        let timestamp = UnixTimestamp::from_be_bytes(timestamp_vec.as_slice().try_into()?);
+
+        Ok(Self {
+            product_id,
+            price_id,
+            price_type,
+            price,
+            expo,
+            twap,
+            twac,
+            confidence_interval,
+            status,
+            corp_act,
+            timestamp,
+        })
     }
 }
 
@@ -405,6 +539,32 @@ mod tests {
         };
     }
 
+    fn mock_attestation(prod: Option<[u8; 32]>, price: Option<[u8; 32]>) -> PriceAttestation {
+        let product_id_bytes = prod.unwrap_or([21u8; 32]);
+        let price_id_bytes = prod.unwrap_or([222u8; 32]);
+        PriceAttestation {
+            product_id: Pubkey::new_from_array(product_id_bytes),
+            price_id: Pubkey::new_from_array(price_id_bytes),
+            price: (0xdeadbeefdeadbabe as u64) as i64,
+            price_type: P2WPriceType::Price,
+            twap: P2WEma {
+                val: -42,
+                numer: 15,
+                denom: 37,
+            },
+            twac: P2WEma {
+                val: 42,
+                numer: 1111,
+                denom: 2222,
+            },
+            expo: -3,
+            status: P2WPriceStatus::Trading,
+            confidence_interval: 101,
+            corp_act: P2WCorpAction::NoCorpAct,
+            timestamp: 123456789i64,
+        }
+    }
+
     #[test]
     fn test_parse_pyth_price_wrong_size_slices() {
         assert!(parse_pyth_price(&[]).is_err());
@@ -412,7 +572,7 @@ mod tests {
     }
 
     #[test]
-    fn test_normal_values() -> SoliResult<()> {
+    fn test_parse_pyth_price() -> SoliResult<()> {
         let price = Price {
             expo: 5,
             agg: PriceInfo {
@@ -431,39 +591,64 @@ mod tests {
     }
 
     #[test]
-    fn test_serialize_deserialize() -> Result<(), ErrBox> {
+    fn test_attestation_serde() -> Result<(), ErrBox> {
         let product_id_bytes = [21u8; 32];
         let price_id_bytes = [222u8; 32];
+        let attestation: PriceAttestation =
+            mock_attestation(Some(product_id_bytes), Some(price_id_bytes));
+
         println!("Hex product_id: {:02X?}", &product_id_bytes);
         println!("Hex price_id: {:02X?}", &price_id_bytes);
-        let attestation: PriceAttestation = PriceAttestation {
-            product_id: Pubkey::new_from_array(product_id_bytes),
-            price_id: Pubkey::new_from_array(price_id_bytes),
-            price: (0xdeadbeefdeadbabe as u64) as i64,
-            price_type: P2WPriceType::Price,
-            twap: P2WEma {
-                val: -42,
-                numer: 15,
-                denom: 37,
-            },
-            twac: P2WEma {
-                val: 42,
-                numer: 1111,
-                denom: 2222,
-            },
-            expo: -3,
-            status: P2WPriceStatus::Trading,
-            confidence_interval: 101,
-            corp_act: P2WCorpAction::NoCorpAct,
-            timestamp: 123456789i64,
-        };
 
         println!("Regular: {:#?}", &attestation);
         println!("Hex: {:#02X?}", &attestation);
-	let bytes = attestation.serialize();
+        let bytes = attestation.serialize();
         println!("Hex Bytes: {:02X?}", bytes);
 
-	assert_eq!(PriceAttestation::deserialize(bytes.as_slice())?, attestation);
+        assert_eq!(
+            PriceAttestation::deserialize(bytes.as_slice())?,
+            attestation
+        );
+        Ok(())
+    }
+
+    #[test]
+    fn test_attestation_serde_wrong_size() -> Result<(), ErrBox> {
+        assert!(PriceAttestation::deserialize(&[][..]).is_err());
+        assert!(PriceAttestation::deserialize(vec![0u8; 1].as_slice()).is_err());
+        Ok(())
+    }
+
+    #[test]
+    fn test_batch_serde() -> Result<(), ErrBox> {
+        let attestations: Vec<_> = (0..65535)
+            .map(|i| mock_attestation(Some([(i % 256) as u8; 32]), None))
+            .collect();
+
+        let serialized = batch_serialize(attestations.iter())?;
+
+        let deserialized = batch_deserialize(serialized.as_slice())?;
+
+        assert_eq!(attestations, deserialized);
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_batch_serde_wrong_size() -> Result<(), ErrBox> {
+        assert!(batch_deserialize(&[][..]).is_err());
+        assert!(batch_deserialize(vec![0u8; 1].as_slice()).is_err());
+
+        let attestations: Vec<_> = (0..20)
+            .map(|i| mock_attestation(Some([(i % 256) as u8; 32]), None))
+            .collect();
+
+        let serialized = batch_serialize(attestations.iter())?;
+
+        // Missing last byte in last attestation must be an error
+        let len = serialized.len();
+        assert!(batch_deserialize(&serialized.as_slice()[..len - 1]).is_err());
+
         Ok(())
     }
 }

+ 10 - 9
solana/pyth2wormhole/program/src/wasm.rs

@@ -4,14 +4,7 @@ use wasm_bindgen::prelude::*;
 
 use std::str::FromStr;
 
-use crate::{attest::P2WEmitter, types::PriceAttestation};
-
-/// sanity check for wasm compilation, TODO(sdrozd): remove after
-/// meaningful endpoints are added
-#[wasm_bindgen]
-pub fn hello_p2w() -> String {
-    "Ciao mondo!".to_owned()
-}
+use crate::{attest::P2WEmitter, types};
 
 #[wasm_bindgen]
 pub fn get_emitter_address(program_id: String) -> Vec<u8> {
@@ -23,7 +16,15 @@ pub fn get_emitter_address(program_id: String) -> Vec<u8> {
 
 #[wasm_bindgen]
 pub fn parse_attestation(bytes: Vec<u8>) -> JsValue {
-    let a = PriceAttestation::deserialize(bytes.as_slice()).unwrap();
+    let a = types::PriceAttestation::deserialize(bytes.as_slice()).unwrap();
     
+    JsValue::from_serde(&a).unwrap()
+}
+
+#[wasm_bindgen]
+pub fn parse_batch_attestation(bytes: Vec<u8>) -> JsValue {
+    let a = types::batch_deserialize(bytes.as_slice()).unwrap();
+    
+
     JsValue::from_serde(&a).unwrap()
 }

+ 12 - 0
solana/solitaire/client/src/lib.rs

@@ -66,6 +66,9 @@ pub enum AccEntry {
     Derived(Pubkey),
     /// Key derived from constants and/or program address, read-only.
     DerivedRO(Pubkey),
+
+    /// Empty value for nullables
+    Empty,
 }
 
 /// Types implementing Wrap are those that can be turned into a
@@ -85,6 +88,15 @@ pub trait Wrap {
     }
 }
 
+impl<T: Wrap> Wrap for Option<T> {
+    fn wrap(a: &AccEntry) -> StdResult<Vec<AccountMeta>, ErrBox> {
+        match a {
+	    AccEntry::Empty => Ok(vec![AccountMeta::new_readonly(Pubkey::new_from_array([0u8; 32]), false)]),
+	    other => T::wrap(other)
+        }
+    }
+}
+
 impl<'a, 'b: 'a, T> Wrap for Signer<T>
 where
     T: Keyed<'a, 'b>,

+ 27 - 0
solana/solitaire/program/src/processors/peel.rs

@@ -17,6 +17,7 @@ use solana_program::{
 use std::marker::PhantomData;
 
 use crate::{
+    trace,
     processors::seeded::{
         AccountOwner,
         Owned,
@@ -41,6 +42,32 @@ pub trait Peel<'a, 'b: 'a, 'c> {
     fn persist(&self, program_id: &Pubkey) -> Result<()>;
 }
 
+/// Peel a nullable value (0-account means None)
+impl<'a, 'b: 'a, 'c, T: Peel<'a, 'b, 'c>> Peel<'a, 'b, 'c> for Option<T> {
+    fn peel<I>(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result<Self> {
+	// Check for 0-account
+	if ctx.info().key == &Pubkey::new_from_array([0u8; 32]) {
+	    trace!(&format!("Peeled {} is None, returning", std::any::type_name::<Option<T>>()));
+	    Ok(None)
+	} else {
+	    Ok(Some(T::peel(ctx)?))
+	}
+    }
+
+    fn deps() -> Vec<Pubkey> {
+	T::deps()
+    }
+
+    fn persist(&self, program_id: &Pubkey) -> Result<()> {
+	if let Some(s) = self.as_ref() {
+            T::persist(s, program_id)
+	} else {
+	    trace!(&format!("Peeled {} is None, not persisting", std::any::type_name::<Option<T>>()));
+	    Ok(())
+	}
+    }
+}
+
 /// Peel a Derived Key
 impl<'a, 'b: 'a, 'c, T: Peel<'a, 'b, 'c>, const Seed: &'static str> Peel<'a, 'b, 'c>
     for Derive<T, Seed>

+ 1 - 0
third_party/pyth/Dockerfile.p2w-attest

@@ -16,3 +16,4 @@ ENV P2W_OWNER_KEYPAIR="/usr/src/solana/keys/p2w_owner.json"
 ENV P2W_ATTESTATIONS_PORT="4343"
 ENV PYTH_PUBLISHER_KEYPAIR="/usr/src/solana/keys/pyth_publisher.json"
 ENV PYTH_PROGRAM_KEYPAIR="/usr/src/solana/keys/pyth_program.json"
+ENV SOL_AIRDROP_AMT="100"

+ 1 - 0
third_party/pyth/Dockerfile.pyth

@@ -44,3 +44,4 @@ USER pyth
 
 ENV PYTH=$PYTH_SRC_ROOT/build/pyth
 ENV READINESS_PORT=2000
+ENV SOL_AIRDROP_AMT=100

+ 33 - 0
third_party/pyth/p2w-relay/.gitignore

@@ -0,0 +1,33 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+
+# testing
+/coverage
+
+# production
+/build
+
+# misc
+.DS_Store
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# ethereum contracts
+/contracts
+/src/*-contracts/
+
+# tsproto output
+/src/proto
+
+# build
+/lib

+ 40 - 0
third_party/pyth/p2w-relay/Dockerfile

@@ -0,0 +1,40 @@
+FROM node:16-alpine@sha256:004dbac84fed48e20f9888a23e32fa7cf83c2995e174a78d41d9a9dd1e051a20
+
+# npm needs a Python for some of the deps
+RUN apk add git python3 make build-base
+
+# Build ETH
+WORKDIR /usr/src/ethereum
+ADD ethereum .
+RUN --mount=type=cache,target=/home/node/.npm \
+  npm ci
+
+# Build Wormhole SDK
+WORKDIR /usr/src/sdk/js
+ADD sdk/js/ .
+RUN --mount=type=cache,target=/home/node/.npm \
+  npm ci && npm run build
+
+# Build p2w-sdk in dir preserving directory structure
+WORKDIR /usr/src/third_party/pyth/p2w-sdk
+COPY third_party/pyth/p2w-sdk/package.json third_party/pyth/p2w-sdk/package-lock.json .
+RUN --mount=type=cache,target=/root/.cache \
+    --mount=type=cache,target=/root/.npm \
+    npm ci
+
+COPY third_party/pyth/p2w-sdk .
+RUN --mount=type=cache,target=/root/.cache \
+    --mount=type=cache,target=/root/.npm \
+    npm run build
+
+# Build p2w-relay
+WORKDIR /usr/src/third_party/pyth/p2w-relay
+COPY third_party/pyth/p2w-relay/package.json third_party/pyth/p2w-relay/package-lock.json .
+RUN --mount=type=cache,target=/root/.cache \
+    --mount=type=cache,target=/root/.npm \
+    npm ci
+
+COPY third_party/pyth/p2w-relay .
+RUN --mount=type=cache,target=/root/.cache \
+    --mount=type=cache,target=/root/.npm \
+    npm run build

+ 24 - 0
third_party/pyth/p2w-relay/README.md

@@ -0,0 +1,24 @@
+# Pyth2wormhole relay example
+IMPORTANT: This is not ready for production.
+
+This package is an example Pyth2wormhole relayer implementation. The
+main focus is to provide an automated integration test that will
+perform last-mile delivery of Pyth2wormhole price attestations.
+
+# How it works
+## Relayer recap
+When attesting with Wormhole, the final step consists of a query for
+the guardian-signed attestation data on the guardian public RPC,
+followed by posting the data to each desired target chain
+contract. Each target chain contract lets callers verify the payload's
+signatures, thus proving its validity. This activity means being
+a Wormhole **relayer**.
+
+## How this package relays attestations
+`p2w-relay` is a Node.js relayer script targeting ETH that will
+periodically query its source-chain counterpart for new sequence
+numbers to query from the guardians. Any pending sequence numbers will
+stick around in a global state until their corresponding messages are
+successfully retrieved from the guardians. Later, target chain calls
+are made and a given seqno is deleted from the pool. Failed target
+chain calls will not be retried.

+ 5038 - 0
third_party/pyth/p2w-relay/package-lock.json

@@ -0,0 +1,5038 @@
+{
+    "name": "@certusone/p2w-relay",
+    "version": "0.1.0",
+    "lockfileVersion": 2,
+    "requires": true,
+    "packages": {
+        "": {
+            "name": "@certusone/p2w-relay",
+            "version": "0.1.0",
+            "license": "MIT",
+            "dependencies": {
+                "@certusone/p2w-sdk": "file:../p2w-sdk",
+                "@certusone/wormhole-sdk": "file:../../../sdk/js",
+                "@improbable-eng/grpc-web-node-http-transport": "^0.14.1",
+                "ts-proto": "^1.83.1"
+            },
+            "devDependencies": {
+                "@openzeppelin/contracts": "^4.2.0",
+                "@typechain/ethers-v5": "^7.1.2",
+                "@types/long": "^4.0.1",
+                "@types/node": "^16.6.1",
+                "copy-dir": "^1.3.0",
+                "esm": "^3.2.25",
+                "ethers": "^5.4.7",
+                "find": "^0.3.0",
+                "prettier": "^2.3.2",
+                "ts-loader": "^9.2.5",
+                "tslint": "^6.1.3",
+                "tslint-config-prettier": "^1.18.0",
+                "typescript": "^4.3.5"
+            }
+        },
+        "../../../sdk/js": {
+            "name": "@certusone/wormhole-sdk",
+            "version": "0.0.5",
+            "license": "Apache-2.0",
+            "dependencies": {
+                "@improbable-eng/grpc-web": "^0.14.0",
+                "@solana/spl-token": "^0.1.8",
+                "@solana/web3.js": "^1.24.0",
+                "@terra-money/terra.js": "^2.0.14",
+                "@terra-money/wallet-provider": "^2.2.0",
+                "bech32": "^2.0.0",
+                "js-base64": "^3.6.1",
+                "protobufjs": "^6.11.2",
+                "rxjs": "^7.3.0"
+            },
+            "devDependencies": {
+                "@openzeppelin/contracts": "^4.2.0",
+                "@typechain/ethers-v5": "^7.0.1",
+                "@types/long": "^4.0.1",
+                "@types/node": "^16.6.1",
+                "@types/react": "^17.0.19",
+                "copy-dir": "^1.3.0",
+                "ethers": "^5.4.4",
+                "prettier": "^2.3.2",
+                "tslint": "^6.1.3",
+                "tslint-config-prettier": "^1.18.0",
+                "typescript": "^4.3.5"
+            }
+        },
+        "../p2w-sdk": {
+            "name": "@certusone/p2w-sdk",
+            "version": "0.1.0",
+            "license": "MIT",
+            "dependencies": {
+                "@certusone/wormhole-sdk": "file:../../../sdk/js",
+                "@improbable-eng/grpc-web-node-http-transport": "^0.14.1"
+            },
+            "devDependencies": {
+                "@openzeppelin/contracts": "^4.2.0",
+                "@typechain/ethers-v5": "^7.1.2",
+                "@types/long": "^4.0.1",
+                "@types/node": "^16.6.1",
+                "copy-dir": "^1.3.0",
+                "find": "^0.3.0",
+                "prettier": "^2.3.2",
+                "tslint": "^6.1.3",
+                "tslint-config-prettier": "^1.18.0",
+                "typescript": "^4.3.5"
+            },
+            "peerDependencies": {
+                "@solana/web3.js": "^1.24.0"
+            }
+        },
+        "node_modules/@babel/code-frame": {
+            "version": "7.15.8",
+            "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.15.8.tgz",
+            "integrity": "sha512-2IAnmn8zbvC/jKYhq5Ki9I+DwjlrtMPUCH/CpHvqI4dNnlwHwsxoIhlc8WcYY5LSYknXQtAlFYuHfqAFCvQ4Wg==",
+            "dev": true,
+            "dependencies": {
+                "@babel/highlight": "^7.14.5"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            }
+        },
+        "node_modules/@babel/helper-validator-identifier": {
+            "version": "7.15.7",
+            "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz",
+            "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==",
+            "dev": true,
+            "engines": {
+                "node": ">=6.9.0"
+            }
+        },
+        "node_modules/@babel/highlight": {
+            "version": "7.14.5",
+            "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz",
+            "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==",
+            "dev": true,
+            "dependencies": {
+                "@babel/helper-validator-identifier": "^7.14.5",
+                "chalk": "^2.0.0",
+                "js-tokens": "^4.0.0"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            }
+        },
+        "node_modules/@certusone/p2w-sdk": {
+            "resolved": "../p2w-sdk",
+            "link": true
+        },
+        "node_modules/@certusone/wormhole-sdk": {
+            "resolved": "../../../sdk/js",
+            "link": true
+        },
+        "node_modules/@ethersproject/abi": {
+            "version": "5.4.1",
+            "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.4.1.tgz",
+            "integrity": "sha512-9mhbjUk76BiSluiiW4BaYyI58KSbDMMQpCLdsAR+RsT2GyATiNYxVv+pGWRrekmsIdY3I+hOqsYQSTkc8L/mcg==",
+            "dev": true,
+            "funding": [
+                {
+                    "type": "individual",
+                    "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+                },
+                {
+                    "type": "individual",
+                    "url": "https://www.buymeacoffee.com/ricmoo"
+                }
+            ],
+            "dependencies": {
+                "@ethersproject/address": "^5.4.0",
+                "@ethersproject/bignumber": "^5.4.0",
+                "@ethersproject/bytes": "^5.4.0",
+                "@ethersproject/constants": "^5.4.0",
+                "@ethersproject/hash": "^5.4.0",
+                "@ethersproject/keccak256": "^5.4.0",
+                "@ethersproject/logger": "^5.4.0",
+                "@ethersproject/properties": "^5.4.0",
+                "@ethersproject/strings": "^5.4.0"
+            }
+        },
+        "node_modules/@ethersproject/abstract-provider": {
+            "version": "5.4.1",
+            "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.4.1.tgz",
+            "integrity": "sha512-3EedfKI3LVpjSKgAxoUaI+gB27frKsxzm+r21w9G60Ugk+3wVLQwhi1LsEJAKNV7WoZc8CIpNrATlL1QFABjtQ==",
+            "dev": true,
+            "funding": [
+                {
+                    "type": "individual",
+                    "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+                },
+                {
+                    "type": "individual",
+                    "url": "https://www.buymeacoffee.com/ricmoo"
+                }
+            ],
+            "dependencies": {
+                "@ethersproject/bignumber": "^5.4.0",
+                "@ethersproject/bytes": "^5.4.0",
+                "@ethersproject/logger": "^5.4.0",
+                "@ethersproject/networks": "^5.4.0",
+                "@ethersproject/properties": "^5.4.0",
+                "@ethersproject/transactions": "^5.4.0",
+                "@ethersproject/web": "^5.4.0"
+            }
+        },
+        "node_modules/@ethersproject/abstract-signer": {
+            "version": "5.4.1",
+            "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.4.1.tgz",
+            "integrity": "sha512-SkkFL5HVq1k4/25dM+NWP9MILgohJCgGv5xT5AcRruGz4ILpfHeBtO/y6j+Z3UN/PAjDeb4P7E51Yh8wcGNLGA==",
+            "dev": true,
+            "funding": [
+                {
+                    "type": "individual",
+                    "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+                },
+                {
+                    "type": "individual",
+                    "url": "https://www.buymeacoffee.com/ricmoo"
+                }
+            ],
+            "dependencies": {
+                "@ethersproject/abstract-provider": "^5.4.0",
+                "@ethersproject/bignumber": "^5.4.0",
+                "@ethersproject/bytes": "^5.4.0",
+                "@ethersproject/logger": "^5.4.0",
+                "@ethersproject/properties": "^5.4.0"
+            }
+        },
+        "node_modules/@ethersproject/address": {
+            "version": "5.4.0",
+            "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.4.0.tgz",
+            "integrity": "sha512-SD0VgOEkcACEG/C6xavlU1Hy3m5DGSXW3CUHkaaEHbAPPsgi0coP5oNPsxau8eTlZOk/bpa/hKeCNoK5IzVI2Q==",
+            "dev": true,
+            "funding": [
+                {
+                    "type": "individual",
+                    "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+                },
+                {
+                    "type": "individual",
+                    "url": "https://www.buymeacoffee.com/ricmoo"
+                }
+            ],
+            "dependencies": {
+                "@ethersproject/bignumber": "^5.4.0",
+                "@ethersproject/bytes": "^5.4.0",
+                "@ethersproject/keccak256": "^5.4.0",
+                "@ethersproject/logger": "^5.4.0",
+                "@ethersproject/rlp": "^5.4.0"
+            }
+        },
+        "node_modules/@ethersproject/base64": {
+            "version": "5.4.0",
+            "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.4.0.tgz",
+            "integrity": "sha512-CjQw6E17QDSSC5jiM9YpF7N1aSCHmYGMt9bWD8PWv6YPMxjsys2/Q8xLrROKI3IWJ7sFfZ8B3flKDTM5wlWuZQ==",
+            "dev": true,
+            "funding": [
+                {
+                    "type": "individual",
+                    "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+                },
+                {
+                    "type": "individual",
+                    "url": "https://www.buymeacoffee.com/ricmoo"
+                }
+            ],
+            "dependencies": {
+                "@ethersproject/bytes": "^5.4.0"
+            }
+        },
+        "node_modules/@ethersproject/basex": {
+            "version": "5.4.0",
+            "resolved": "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.4.0.tgz",
+            "integrity": "sha512-J07+QCVJ7np2bcpxydFVf/CuYo9mZ7T73Pe7KQY4c1lRlrixMeblauMxHXD0MPwFmUHZIILDNViVkykFBZylbg==",
+            "dev": true,
+            "funding": [
+                {
+                    "type": "individual",
+                    "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+                },
+                {
+                    "type": "individual",
+                    "url": "https://www.buymeacoffee.com/ricmoo"
+                }
+            ],
+            "dependencies": {
+                "@ethersproject/bytes": "^5.4.0",
+                "@ethersproject/properties": "^5.4.0"
+            }
+        },
+        "node_modules/@ethersproject/bignumber": {
+            "version": "5.4.2",
+            "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.4.2.tgz",
+            "integrity": "sha512-oIBDhsKy5bs7j36JlaTzFgNPaZjiNDOXsdSgSpXRucUl+UA6L/1YLlFeI3cPAoodcenzF4nxNPV13pcy7XbWjA==",
+            "dev": true,
+            "funding": [
+                {
+                    "type": "individual",
+                    "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+                },
+                {
+                    "type": "individual",
+                    "url": "https://www.buymeacoffee.com/ricmoo"
+                }
+            ],
+            "dependencies": {
+                "@ethersproject/bytes": "^5.4.0",
+                "@ethersproject/logger": "^5.4.0",
+                "bn.js": "^4.11.9"
+            }
+        },
+        "node_modules/@ethersproject/bytes": {
+            "version": "5.4.0",
+            "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.4.0.tgz",
+            "integrity": "sha512-H60ceqgTHbhzOj4uRc/83SCN9d+BSUnOkrr2intevqdtEMO1JFVZ1XL84OEZV+QjV36OaZYxtnt4lGmxcGsPfA==",
+            "dev": true,
+            "funding": [
+                {
+                    "type": "individual",
+                    "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+                },
+                {
+                    "type": "individual",
+                    "url": "https://www.buymeacoffee.com/ricmoo"
+                }
+            ],
+            "dependencies": {
+                "@ethersproject/logger": "^5.4.0"
+            }
+        },
+        "node_modules/@ethersproject/constants": {
+            "version": "5.4.0",
+            "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.4.0.tgz",
+            "integrity": "sha512-tzjn6S7sj9+DIIeKTJLjK9WGN2Tj0P++Z8ONEIlZjyoTkBuODN+0VfhAyYksKi43l1Sx9tX2VlFfzjfmr5Wl3Q==",
+            "dev": true,
+            "funding": [
+                {
+                    "type": "individual",
+                    "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+                },
+                {
+                    "type": "individual",
+                    "url": "https://www.buymeacoffee.com/ricmoo"
+                }
+            ],
+            "dependencies": {
+                "@ethersproject/bignumber": "^5.4.0"
+            }
+        },
+        "node_modules/@ethersproject/contracts": {
+            "version": "5.4.1",
+            "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.4.1.tgz",
+            "integrity": "sha512-m+z2ZgPy4pyR15Je//dUaymRUZq5MtDajF6GwFbGAVmKz/RF+DNIPwF0k5qEcL3wPGVqUjFg2/krlCRVTU4T5w==",
+            "dev": true,
+            "funding": [
+                {
+                    "type": "individual",
+                    "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+                },
+                {
+                    "type": "individual",
+                    "url": "https://www.buymeacoffee.com/ricmoo"
+                }
+            ],
+            "dependencies": {
+                "@ethersproject/abi": "^5.4.0",
+                "@ethersproject/abstract-provider": "^5.4.0",
+                "@ethersproject/abstract-signer": "^5.4.0",
+                "@ethersproject/address": "^5.4.0",
+                "@ethersproject/bignumber": "^5.4.0",
+                "@ethersproject/bytes": "^5.4.0",
+                "@ethersproject/constants": "^5.4.0",
+                "@ethersproject/logger": "^5.4.0",
+                "@ethersproject/properties": "^5.4.0",
+                "@ethersproject/transactions": "^5.4.0"
+            }
+        },
+        "node_modules/@ethersproject/hash": {
+            "version": "5.4.0",
+            "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.4.0.tgz",
+            "integrity": "sha512-xymAM9tmikKgbktOCjW60Z5sdouiIIurkZUr9oW5NOex5uwxrbsYG09kb5bMcNjlVeJD3yPivTNzViIs1GCbqA==",
+            "dev": true,
+            "funding": [
+                {
+                    "type": "individual",
+                    "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+                },
+                {
+                    "type": "individual",
+                    "url": "https://www.buymeacoffee.com/ricmoo"
+                }
+            ],
+            "dependencies": {
+                "@ethersproject/abstract-signer": "^5.4.0",
+                "@ethersproject/address": "^5.4.0",
+                "@ethersproject/bignumber": "^5.4.0",
+                "@ethersproject/bytes": "^5.4.0",
+                "@ethersproject/keccak256": "^5.4.0",
+                "@ethersproject/logger": "^5.4.0",
+                "@ethersproject/properties": "^5.4.0",
+                "@ethersproject/strings": "^5.4.0"
+            }
+        },
+        "node_modules/@ethersproject/hdnode": {
+            "version": "5.4.0",
+            "resolved": "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.4.0.tgz",
+            "integrity": "sha512-pKxdS0KAaeVGfZPp1KOiDLB0jba11tG6OP1u11QnYfb7pXn6IZx0xceqWRr6ygke8+Kw74IpOoSi7/DwANhy8Q==",
+            "dev": true,
+            "funding": [
+                {
+                    "type": "individual",
+                    "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+                },
+                {
+                    "type": "individual",
+                    "url": "https://www.buymeacoffee.com/ricmoo"
+                }
+            ],
+            "dependencies": {
+                "@ethersproject/abstract-signer": "^5.4.0",
+                "@ethersproject/basex": "^5.4.0",
+                "@ethersproject/bignumber": "^5.4.0",
+                "@ethersproject/bytes": "^5.4.0",
+                "@ethersproject/logger": "^5.4.0",
+                "@ethersproject/pbkdf2": "^5.4.0",
+                "@ethersproject/properties": "^5.4.0",
+                "@ethersproject/sha2": "^5.4.0",
+                "@ethersproject/signing-key": "^5.4.0",
+                "@ethersproject/strings": "^5.4.0",
+                "@ethersproject/transactions": "^5.4.0",
+                "@ethersproject/wordlists": "^5.4.0"
+            }
+        },
+        "node_modules/@ethersproject/json-wallets": {
+            "version": "5.4.0",
+            "resolved": "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.4.0.tgz",
+            "integrity": "sha512-igWcu3fx4aiczrzEHwG1xJZo9l1cFfQOWzTqwRw/xcvxTk58q4f9M7cjh51EKphMHvrJtcezJ1gf1q1AUOfEQQ==",
+            "dev": true,
+            "funding": [
+                {
+                    "type": "individual",
+                    "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+                },
+                {
+                    "type": "individual",
+                    "url": "https://www.buymeacoffee.com/ricmoo"
+                }
+            ],
+            "dependencies": {
+                "@ethersproject/abstract-signer": "^5.4.0",
+                "@ethersproject/address": "^5.4.0",
+                "@ethersproject/bytes": "^5.4.0",
+                "@ethersproject/hdnode": "^5.4.0",
+                "@ethersproject/keccak256": "^5.4.0",
+                "@ethersproject/logger": "^5.4.0",
+                "@ethersproject/pbkdf2": "^5.4.0",
+                "@ethersproject/properties": "^5.4.0",
+                "@ethersproject/random": "^5.4.0",
+                "@ethersproject/strings": "^5.4.0",
+                "@ethersproject/transactions": "^5.4.0",
+                "aes-js": "3.0.0",
+                "scrypt-js": "3.0.1"
+            }
+        },
+        "node_modules/@ethersproject/keccak256": {
+            "version": "5.4.0",
+            "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.4.0.tgz",
+            "integrity": "sha512-FBI1plWet+dPUvAzPAeHzRKiPpETQzqSUWR1wXJGHVWi4i8bOSrpC3NwpkPjgeXG7MnugVc1B42VbfnQikyC/A==",
+            "dev": true,
+            "funding": [
+                {
+                    "type": "individual",
+                    "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+                },
+                {
+                    "type": "individual",
+                    "url": "https://www.buymeacoffee.com/ricmoo"
+                }
+            ],
+            "dependencies": {
+                "@ethersproject/bytes": "^5.4.0",
+                "js-sha3": "0.5.7"
+            }
+        },
+        "node_modules/@ethersproject/logger": {
+            "version": "5.4.1",
+            "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.4.1.tgz",
+            "integrity": "sha512-DZ+bRinnYLPw1yAC64oRl0QyVZj43QeHIhVKfD/+YwSz4wsv1pfwb5SOFjz+r710YEWzU6LrhuSjpSO+6PeE4A==",
+            "dev": true,
+            "funding": [
+                {
+                    "type": "individual",
+                    "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+                },
+                {
+                    "type": "individual",
+                    "url": "https://www.buymeacoffee.com/ricmoo"
+                }
+            ]
+        },
+        "node_modules/@ethersproject/networks": {
+            "version": "5.4.2",
+            "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.4.2.tgz",
+            "integrity": "sha512-eekOhvJyBnuibfJnhtK46b8HimBc5+4gqpvd1/H9LEl7Q7/qhsIhM81dI9Fcnjpk3jB1aTy6bj0hz3cifhNeYw==",
+            "dev": true,
+            "funding": [
+                {
+                    "type": "individual",
+                    "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+                },
+                {
+                    "type": "individual",
+                    "url": "https://www.buymeacoffee.com/ricmoo"
+                }
+            ],
+            "dependencies": {
+                "@ethersproject/logger": "^5.4.0"
+            }
+        },
+        "node_modules/@ethersproject/pbkdf2": {
+            "version": "5.4.0",
+            "resolved": "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.4.0.tgz",
+            "integrity": "sha512-x94aIv6tiA04g6BnazZSLoRXqyusawRyZWlUhKip2jvoLpzJuLb//KtMM6PEovE47pMbW+Qe1uw+68ameJjB7g==",
+            "dev": true,
+            "funding": [
+                {
+                    "type": "individual",
+                    "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+                },
+                {
+                    "type": "individual",
+                    "url": "https://www.buymeacoffee.com/ricmoo"
+                }
+            ],
+            "dependencies": {
+                "@ethersproject/bytes": "^5.4.0",
+                "@ethersproject/sha2": "^5.4.0"
+            }
+        },
+        "node_modules/@ethersproject/properties": {
+            "version": "5.4.1",
+            "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.4.1.tgz",
+            "integrity": "sha512-cyCGlF8wWlIZyizsj2PpbJ9I7rIlUAfnHYwy/T90pdkSn/NFTa5YWZx2wTJBe9V7dD65dcrrEMisCRUJiq6n3w==",
+            "dev": true,
+            "funding": [
+                {
+                    "type": "individual",
+                    "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+                },
+                {
+                    "type": "individual",
+                    "url": "https://www.buymeacoffee.com/ricmoo"
+                }
+            ],
+            "dependencies": {
+                "@ethersproject/logger": "^5.4.0"
+            }
+        },
+        "node_modules/@ethersproject/providers": {
+            "version": "5.4.5",
+            "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.4.5.tgz",
+            "integrity": "sha512-1GkrvkiAw3Fj28cwi1Sqm8ED1RtERtpdXmRfwIBGmqBSN5MoeRUHuwHPppMtbPayPgpFcvD7/Gdc9doO5fGYgw==",
+            "dev": true,
+            "funding": [
+                {
+                    "type": "individual",
+                    "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+                },
+                {
+                    "type": "individual",
+                    "url": "https://www.buymeacoffee.com/ricmoo"
+                }
+            ],
+            "dependencies": {
+                "@ethersproject/abstract-provider": "^5.4.0",
+                "@ethersproject/abstract-signer": "^5.4.0",
+                "@ethersproject/address": "^5.4.0",
+                "@ethersproject/basex": "^5.4.0",
+                "@ethersproject/bignumber": "^5.4.0",
+                "@ethersproject/bytes": "^5.4.0",
+                "@ethersproject/constants": "^5.4.0",
+                "@ethersproject/hash": "^5.4.0",
+                "@ethersproject/logger": "^5.4.0",
+                "@ethersproject/networks": "^5.4.0",
+                "@ethersproject/properties": "^5.4.0",
+                "@ethersproject/random": "^5.4.0",
+                "@ethersproject/rlp": "^5.4.0",
+                "@ethersproject/sha2": "^5.4.0",
+                "@ethersproject/strings": "^5.4.0",
+                "@ethersproject/transactions": "^5.4.0",
+                "@ethersproject/web": "^5.4.0",
+                "bech32": "1.1.4",
+                "ws": "7.4.6"
+            }
+        },
+        "node_modules/@ethersproject/random": {
+            "version": "5.4.0",
+            "resolved": "https://registry.npmjs.org/@ethersproject/random/-/random-5.4.0.tgz",
+            "integrity": "sha512-pnpWNQlf0VAZDEOVp1rsYQosmv2o0ITS/PecNw+mS2/btF8eYdspkN0vIXrCMtkX09EAh9bdk8GoXmFXM1eAKw==",
+            "dev": true,
+            "funding": [
+                {
+                    "type": "individual",
+                    "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+                },
+                {
+                    "type": "individual",
+                    "url": "https://www.buymeacoffee.com/ricmoo"
+                }
+            ],
+            "dependencies": {
+                "@ethersproject/bytes": "^5.4.0",
+                "@ethersproject/logger": "^5.4.0"
+            }
+        },
+        "node_modules/@ethersproject/rlp": {
+            "version": "5.4.0",
+            "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.4.0.tgz",
+            "integrity": "sha512-0I7MZKfi+T5+G8atId9QaQKHRvvasM/kqLyAH4XxBCBchAooH2EX5rL9kYZWwcm3awYV+XC7VF6nLhfeQFKVPg==",
+            "dev": true,
+            "funding": [
+                {
+                    "type": "individual",
+                    "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+                },
+                {
+                    "type": "individual",
+                    "url": "https://www.buymeacoffee.com/ricmoo"
+                }
+            ],
+            "dependencies": {
+                "@ethersproject/bytes": "^5.4.0",
+                "@ethersproject/logger": "^5.4.0"
+            }
+        },
+        "node_modules/@ethersproject/sha2": {
+            "version": "5.4.0",
+            "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.4.0.tgz",
+            "integrity": "sha512-siheo36r1WD7Cy+bDdE1BJ8y0bDtqXCOxRMzPa4bV1TGt/eTUUt03BHoJNB6reWJD8A30E/pdJ8WFkq+/uz4Gg==",
+            "dev": true,
+            "funding": [
+                {
+                    "type": "individual",
+                    "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+                },
+                {
+                    "type": "individual",
+                    "url": "https://www.buymeacoffee.com/ricmoo"
+                }
+            ],
+            "dependencies": {
+                "@ethersproject/bytes": "^5.4.0",
+                "@ethersproject/logger": "^5.4.0",
+                "hash.js": "1.1.7"
+            }
+        },
+        "node_modules/@ethersproject/signing-key": {
+            "version": "5.4.0",
+            "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.4.0.tgz",
+            "integrity": "sha512-q8POUeywx6AKg2/jX9qBYZIAmKSB4ubGXdQ88l40hmATj29JnG5pp331nAWwwxPn2Qao4JpWHNZsQN+bPiSW9A==",
+            "dev": true,
+            "funding": [
+                {
+                    "type": "individual",
+                    "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+                },
+                {
+                    "type": "individual",
+                    "url": "https://www.buymeacoffee.com/ricmoo"
+                }
+            ],
+            "dependencies": {
+                "@ethersproject/bytes": "^5.4.0",
+                "@ethersproject/logger": "^5.4.0",
+                "@ethersproject/properties": "^5.4.0",
+                "bn.js": "^4.11.9",
+                "elliptic": "6.5.4",
+                "hash.js": "1.1.7"
+            }
+        },
+        "node_modules/@ethersproject/solidity": {
+            "version": "5.4.0",
+            "resolved": "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.4.0.tgz",
+            "integrity": "sha512-XFQTZ7wFSHOhHcV1DpcWj7VXECEiSrBuv7JErJvB9Uo+KfCdc3QtUZV+Vjh/AAaYgezUEKbCtE6Khjm44seevQ==",
+            "dev": true,
+            "funding": [
+                {
+                    "type": "individual",
+                    "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+                },
+                {
+                    "type": "individual",
+                    "url": "https://www.buymeacoffee.com/ricmoo"
+                }
+            ],
+            "dependencies": {
+                "@ethersproject/bignumber": "^5.4.0",
+                "@ethersproject/bytes": "^5.4.0",
+                "@ethersproject/keccak256": "^5.4.0",
+                "@ethersproject/sha2": "^5.4.0",
+                "@ethersproject/strings": "^5.4.0"
+            }
+        },
+        "node_modules/@ethersproject/strings": {
+            "version": "5.4.0",
+            "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.4.0.tgz",
+            "integrity": "sha512-k/9DkH5UGDhv7aReXLluFG5ExurwtIpUfnDNhQA29w896Dw3i4uDTz01Quaptbks1Uj9kI8wo9tmW73wcIEaWA==",
+            "dev": true,
+            "funding": [
+                {
+                    "type": "individual",
+                    "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+                },
+                {
+                    "type": "individual",
+                    "url": "https://www.buymeacoffee.com/ricmoo"
+                }
+            ],
+            "dependencies": {
+                "@ethersproject/bytes": "^5.4.0",
+                "@ethersproject/constants": "^5.4.0",
+                "@ethersproject/logger": "^5.4.0"
+            }
+        },
+        "node_modules/@ethersproject/transactions": {
+            "version": "5.4.0",
+            "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.4.0.tgz",
+            "integrity": "sha512-s3EjZZt7xa4BkLknJZ98QGoIza94rVjaEed0rzZ/jB9WrIuu/1+tjvYCWzVrystXtDswy7TPBeIepyXwSYa4WQ==",
+            "dev": true,
+            "funding": [
+                {
+                    "type": "individual",
+                    "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+                },
+                {
+                    "type": "individual",
+                    "url": "https://www.buymeacoffee.com/ricmoo"
+                }
+            ],
+            "dependencies": {
+                "@ethersproject/address": "^5.4.0",
+                "@ethersproject/bignumber": "^5.4.0",
+                "@ethersproject/bytes": "^5.4.0",
+                "@ethersproject/constants": "^5.4.0",
+                "@ethersproject/keccak256": "^5.4.0",
+                "@ethersproject/logger": "^5.4.0",
+                "@ethersproject/properties": "^5.4.0",
+                "@ethersproject/rlp": "^5.4.0",
+                "@ethersproject/signing-key": "^5.4.0"
+            }
+        },
+        "node_modules/@ethersproject/units": {
+            "version": "5.4.0",
+            "resolved": "https://registry.npmjs.org/@ethersproject/units/-/units-5.4.0.tgz",
+            "integrity": "sha512-Z88krX40KCp+JqPCP5oPv5p750g+uU6gopDYRTBGcDvOASh6qhiEYCRatuM/suC4S2XW9Zz90QI35MfSrTIaFg==",
+            "dev": true,
+            "funding": [
+                {
+                    "type": "individual",
+                    "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+                },
+                {
+                    "type": "individual",
+                    "url": "https://www.buymeacoffee.com/ricmoo"
+                }
+            ],
+            "dependencies": {
+                "@ethersproject/bignumber": "^5.4.0",
+                "@ethersproject/constants": "^5.4.0",
+                "@ethersproject/logger": "^5.4.0"
+            }
+        },
+        "node_modules/@ethersproject/wallet": {
+            "version": "5.4.0",
+            "resolved": "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.4.0.tgz",
+            "integrity": "sha512-wU29majLjM6AjCjpat21mPPviG+EpK7wY1+jzKD0fg3ui5fgedf2zEu1RDgpfIMsfn8fJHJuzM4zXZ2+hSHaSQ==",
+            "dev": true,
+            "funding": [
+                {
+                    "type": "individual",
+                    "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+                },
+                {
+                    "type": "individual",
+                    "url": "https://www.buymeacoffee.com/ricmoo"
+                }
+            ],
+            "dependencies": {
+                "@ethersproject/abstract-provider": "^5.4.0",
+                "@ethersproject/abstract-signer": "^5.4.0",
+                "@ethersproject/address": "^5.4.0",
+                "@ethersproject/bignumber": "^5.4.0",
+                "@ethersproject/bytes": "^5.4.0",
+                "@ethersproject/hash": "^5.4.0",
+                "@ethersproject/hdnode": "^5.4.0",
+                "@ethersproject/json-wallets": "^5.4.0",
+                "@ethersproject/keccak256": "^5.4.0",
+                "@ethersproject/logger": "^5.4.0",
+                "@ethersproject/properties": "^5.4.0",
+                "@ethersproject/random": "^5.4.0",
+                "@ethersproject/signing-key": "^5.4.0",
+                "@ethersproject/transactions": "^5.4.0",
+                "@ethersproject/wordlists": "^5.4.0"
+            }
+        },
+        "node_modules/@ethersproject/web": {
+            "version": "5.4.0",
+            "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.4.0.tgz",
+            "integrity": "sha512-1bUusGmcoRLYgMn6c1BLk1tOKUIFuTg8j+6N8lYlbMpDesnle+i3pGSagGNvwjaiLo4Y5gBibwctpPRmjrh4Og==",
+            "dev": true,
+            "funding": [
+                {
+                    "type": "individual",
+                    "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+                },
+                {
+                    "type": "individual",
+                    "url": "https://www.buymeacoffee.com/ricmoo"
+                }
+            ],
+            "dependencies": {
+                "@ethersproject/base64": "^5.4.0",
+                "@ethersproject/bytes": "^5.4.0",
+                "@ethersproject/logger": "^5.4.0",
+                "@ethersproject/properties": "^5.4.0",
+                "@ethersproject/strings": "^5.4.0"
+            }
+        },
+        "node_modules/@ethersproject/wordlists": {
+            "version": "5.4.0",
+            "resolved": "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.4.0.tgz",
+            "integrity": "sha512-FemEkf6a+EBKEPxlzeVgUaVSodU7G0Na89jqKjmWMlDB0tomoU8RlEMgUvXyqtrg8N4cwpLh8nyRnm1Nay1isA==",
+            "dev": true,
+            "funding": [
+                {
+                    "type": "individual",
+                    "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+                },
+                {
+                    "type": "individual",
+                    "url": "https://www.buymeacoffee.com/ricmoo"
+                }
+            ],
+            "dependencies": {
+                "@ethersproject/bytes": "^5.4.0",
+                "@ethersproject/hash": "^5.4.0",
+                "@ethersproject/logger": "^5.4.0",
+                "@ethersproject/properties": "^5.4.0",
+                "@ethersproject/strings": "^5.4.0"
+            }
+        },
+        "node_modules/@improbable-eng/grpc-web": {
+            "version": "0.14.1",
+            "resolved": "https://registry.npmjs.org/@improbable-eng/grpc-web/-/grpc-web-0.14.1.tgz",
+            "integrity": "sha512-XaIYuunepPxoiGVLLHmlnVminUGzBTnXr8Wv7khzmLWbNw4TCwJKX09GSMJlKhu/TRk6gms0ySFxewaETSBqgw==",
+            "peer": true,
+            "dependencies": {
+                "browser-headers": "^0.4.1"
+            },
+            "peerDependencies": {
+                "google-protobuf": "^3.14.0"
+            }
+        },
+        "node_modules/@improbable-eng/grpc-web-node-http-transport": {
+            "version": "0.14.1",
+            "resolved": "https://registry.npmjs.org/@improbable-eng/grpc-web-node-http-transport/-/grpc-web-node-http-transport-0.14.1.tgz",
+            "integrity": "sha512-ZsCTzI1iKUbmQjB5DNZSI5/hvdliuaPpS2h8mVj1QzynL3IFb5NrNnHVHbfcH1wbm26Ka6Z1CrKFGvKLrmbFIg==",
+            "peerDependencies": {
+                "@improbable-eng/grpc-web": ">=0.13.0"
+            }
+        },
+        "node_modules/@openzeppelin/contracts": {
+            "version": "4.3.1",
+            "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-4.3.1.tgz",
+            "integrity": "sha512-QjgbPPlmDK2clK1hzjw2ROfY8KA5q+PfhDUUxZFEBCZP9fi6d5FuNoh/Uq0oCTMEKPmue69vhX2jcl0N/tFKGw==",
+            "dev": true
+        },
+        "node_modules/@protobufjs/aspromise": {
+            "version": "1.1.2",
+            "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
+            "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78="
+        },
+        "node_modules/@protobufjs/base64": {
+            "version": "1.1.2",
+            "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
+            "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="
+        },
+        "node_modules/@protobufjs/codegen": {
+            "version": "2.0.4",
+            "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
+            "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="
+        },
+        "node_modules/@protobufjs/eventemitter": {
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
+            "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A="
+        },
+        "node_modules/@protobufjs/fetch": {
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
+            "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=",
+            "dependencies": {
+                "@protobufjs/aspromise": "^1.1.1",
+                "@protobufjs/inquire": "^1.1.0"
+            }
+        },
+        "node_modules/@protobufjs/float": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
+            "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E="
+        },
+        "node_modules/@protobufjs/inquire": {
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
+            "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik="
+        },
+        "node_modules/@protobufjs/path": {
+            "version": "1.1.2",
+            "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
+            "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0="
+        },
+        "node_modules/@protobufjs/pool": {
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
+            "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q="
+        },
+        "node_modules/@protobufjs/utf8": {
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
+            "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA="
+        },
+        "node_modules/@typechain/ethers-v5": {
+            "version": "7.1.2",
+            "resolved": "https://registry.npmjs.org/@typechain/ethers-v5/-/ethers-v5-7.1.2.tgz",
+            "integrity": "sha512-sD4HVkTL5aIJa3Ft+CmqiOapba0zzZ8xa+QywcWH40Rm/dcxvZWwcCMnnI3En0JebkxOcAVfH3do+kQ9rKSxYw==",
+            "dev": true,
+            "dependencies": {
+                "lodash": "^4.17.15",
+                "ts-essentials": "^7.0.1"
+            },
+            "peerDependencies": {
+                "@ethersproject/abi": "^5.0.0",
+                "@ethersproject/bytes": "^5.0.0",
+                "@ethersproject/providers": "^5.0.0",
+                "ethers": "^5.1.3",
+                "typechain": "^5.0.0",
+                "typescript": ">=4.0.0"
+            }
+        },
+        "node_modules/@types/eslint": {
+            "version": "7.28.0",
+            "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.28.0.tgz",
+            "integrity": "sha512-07XlgzX0YJUn4iG1ocY4IX9DzKSmMGUs6ESKlxWhZRaa0fatIWaHWUVapcuGa8r5HFnTqzj+4OCjd5f7EZ/i/A==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "@types/estree": "*",
+                "@types/json-schema": "*"
+            }
+        },
+        "node_modules/@types/eslint-scope": {
+            "version": "3.7.1",
+            "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.1.tgz",
+            "integrity": "sha512-SCFeogqiptms4Fg29WpOTk5nHIzfpKCemSN63ksBQYKTcXoJEmJagV+DhVmbapZzY4/5YaOV1nZwrsU79fFm1g==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "@types/eslint": "*",
+                "@types/estree": "*"
+            }
+        },
+        "node_modules/@types/estree": {
+            "version": "0.0.50",
+            "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz",
+            "integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==",
+            "dev": true,
+            "peer": true
+        },
+        "node_modules/@types/json-schema": {
+            "version": "7.0.9",
+            "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz",
+            "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==",
+            "dev": true,
+            "peer": true
+        },
+        "node_modules/@types/long": {
+            "version": "4.0.1",
+            "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz",
+            "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w=="
+        },
+        "node_modules/@types/node": {
+            "version": "16.9.1",
+            "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.1.tgz",
+            "integrity": "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g=="
+        },
+        "node_modules/@types/object-hash": {
+            "version": "1.3.4",
+            "resolved": "https://registry.npmjs.org/@types/object-hash/-/object-hash-1.3.4.tgz",
+            "integrity": "sha512-xFdpkAkikBgqBdG9vIlsqffDV8GpvnPEzs0IUtr1v3BEB97ijsFQ4RXVbUZwjFThhB4MDSTUfvmxUD5PGx0wXA=="
+        },
+        "node_modules/@types/prettier": {
+            "version": "2.3.2",
+            "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.3.2.tgz",
+            "integrity": "sha512-eI5Yrz3Qv4KPUa/nSIAi0h+qX0XyewOliug5F2QAtuRg6Kjg6jfmxe1GIwoIRhZspD1A0RP8ANrPwvEXXtRFog==",
+            "dev": true,
+            "peer": true
+        },
+        "node_modules/@webassemblyjs/ast": {
+            "version": "1.11.1",
+            "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz",
+            "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "@webassemblyjs/helper-numbers": "1.11.1",
+                "@webassemblyjs/helper-wasm-bytecode": "1.11.1"
+            }
+        },
+        "node_modules/@webassemblyjs/floating-point-hex-parser": {
+            "version": "1.11.1",
+            "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz",
+            "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==",
+            "dev": true,
+            "peer": true
+        },
+        "node_modules/@webassemblyjs/helper-api-error": {
+            "version": "1.11.1",
+            "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz",
+            "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==",
+            "dev": true,
+            "peer": true
+        },
+        "node_modules/@webassemblyjs/helper-buffer": {
+            "version": "1.11.1",
+            "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz",
+            "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==",
+            "dev": true,
+            "peer": true
+        },
+        "node_modules/@webassemblyjs/helper-numbers": {
+            "version": "1.11.1",
+            "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz",
+            "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "@webassemblyjs/floating-point-hex-parser": "1.11.1",
+                "@webassemblyjs/helper-api-error": "1.11.1",
+                "@xtuc/long": "4.2.2"
+            }
+        },
+        "node_modules/@webassemblyjs/helper-wasm-bytecode": {
+            "version": "1.11.1",
+            "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz",
+            "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==",
+            "dev": true,
+            "peer": true
+        },
+        "node_modules/@webassemblyjs/helper-wasm-section": {
+            "version": "1.11.1",
+            "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz",
+            "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "@webassemblyjs/ast": "1.11.1",
+                "@webassemblyjs/helper-buffer": "1.11.1",
+                "@webassemblyjs/helper-wasm-bytecode": "1.11.1",
+                "@webassemblyjs/wasm-gen": "1.11.1"
+            }
+        },
+        "node_modules/@webassemblyjs/ieee754": {
+            "version": "1.11.1",
+            "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz",
+            "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "@xtuc/ieee754": "^1.2.0"
+            }
+        },
+        "node_modules/@webassemblyjs/leb128": {
+            "version": "1.11.1",
+            "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz",
+            "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "@xtuc/long": "4.2.2"
+            }
+        },
+        "node_modules/@webassemblyjs/utf8": {
+            "version": "1.11.1",
+            "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz",
+            "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==",
+            "dev": true,
+            "peer": true
+        },
+        "node_modules/@webassemblyjs/wasm-edit": {
+            "version": "1.11.1",
+            "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz",
+            "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "@webassemblyjs/ast": "1.11.1",
+                "@webassemblyjs/helper-buffer": "1.11.1",
+                "@webassemblyjs/helper-wasm-bytecode": "1.11.1",
+                "@webassemblyjs/helper-wasm-section": "1.11.1",
+                "@webassemblyjs/wasm-gen": "1.11.1",
+                "@webassemblyjs/wasm-opt": "1.11.1",
+                "@webassemblyjs/wasm-parser": "1.11.1",
+                "@webassemblyjs/wast-printer": "1.11.1"
+            }
+        },
+        "node_modules/@webassemblyjs/wasm-gen": {
+            "version": "1.11.1",
+            "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz",
+            "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "@webassemblyjs/ast": "1.11.1",
+                "@webassemblyjs/helper-wasm-bytecode": "1.11.1",
+                "@webassemblyjs/ieee754": "1.11.1",
+                "@webassemblyjs/leb128": "1.11.1",
+                "@webassemblyjs/utf8": "1.11.1"
+            }
+        },
+        "node_modules/@webassemblyjs/wasm-opt": {
+            "version": "1.11.1",
+            "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz",
+            "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "@webassemblyjs/ast": "1.11.1",
+                "@webassemblyjs/helper-buffer": "1.11.1",
+                "@webassemblyjs/wasm-gen": "1.11.1",
+                "@webassemblyjs/wasm-parser": "1.11.1"
+            }
+        },
+        "node_modules/@webassemblyjs/wasm-parser": {
+            "version": "1.11.1",
+            "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz",
+            "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "@webassemblyjs/ast": "1.11.1",
+                "@webassemblyjs/helper-api-error": "1.11.1",
+                "@webassemblyjs/helper-wasm-bytecode": "1.11.1",
+                "@webassemblyjs/ieee754": "1.11.1",
+                "@webassemblyjs/leb128": "1.11.1",
+                "@webassemblyjs/utf8": "1.11.1"
+            }
+        },
+        "node_modules/@webassemblyjs/wast-printer": {
+            "version": "1.11.1",
+            "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz",
+            "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "@webassemblyjs/ast": "1.11.1",
+                "@xtuc/long": "4.2.2"
+            }
+        },
+        "node_modules/@xtuc/ieee754": {
+            "version": "1.2.0",
+            "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
+            "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==",
+            "dev": true,
+            "peer": true
+        },
+        "node_modules/@xtuc/long": {
+            "version": "4.2.2",
+            "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
+            "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
+            "dev": true,
+            "peer": true
+        },
+        "node_modules/acorn": {
+            "version": "8.5.0",
+            "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz",
+            "integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==",
+            "dev": true,
+            "peer": true,
+            "bin": {
+                "acorn": "bin/acorn"
+            },
+            "engines": {
+                "node": ">=0.4.0"
+            }
+        },
+        "node_modules/acorn-import-assertions": {
+            "version": "1.7.6",
+            "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.7.6.tgz",
+            "integrity": "sha512-FlVvVFA1TX6l3lp8VjDnYYq7R1nyW6x3svAt4nDgrWQ9SBaSh9CnbwgSUTasgfNfOG5HlM1ehugCvM+hjo56LA==",
+            "dev": true,
+            "peer": true,
+            "peerDependencies": {
+                "acorn": "^8"
+            }
+        },
+        "node_modules/aes-js": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz",
+            "integrity": "sha1-4h3xCtbCBTKVvLuNq0Cwnb6ofk0=",
+            "dev": true
+        },
+        "node_modules/ajv": {
+            "version": "6.12.6",
+            "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+            "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "fast-deep-equal": "^3.1.1",
+                "fast-json-stable-stringify": "^2.0.0",
+                "json-schema-traverse": "^0.4.1",
+                "uri-js": "^4.2.2"
+            },
+            "funding": {
+                "type": "github",
+                "url": "https://github.com/sponsors/epoberezkin"
+            }
+        },
+        "node_modules/ajv-keywords": {
+            "version": "3.5.2",
+            "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
+            "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
+            "dev": true,
+            "peer": true,
+            "peerDependencies": {
+                "ajv": "^6.9.1"
+            }
+        },
+        "node_modules/ansi-styles": {
+            "version": "3.2.1",
+            "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+            "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+            "dev": true,
+            "dependencies": {
+                "color-convert": "^1.9.0"
+            },
+            "engines": {
+                "node": ">=4"
+            }
+        },
+        "node_modules/argparse": {
+            "version": "1.0.10",
+            "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+            "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+            "dev": true,
+            "dependencies": {
+                "sprintf-js": "~1.0.2"
+            }
+        },
+        "node_modules/array-back": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/array-back/-/array-back-2.0.0.tgz",
+            "integrity": "sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "typical": "^2.6.1"
+            },
+            "engines": {
+                "node": ">=4"
+            }
+        },
+        "node_modules/balanced-match": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+            "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+            "dev": true
+        },
+        "node_modules/bech32": {
+            "version": "1.1.4",
+            "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz",
+            "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==",
+            "dev": true
+        },
+        "node_modules/bn.js": {
+            "version": "4.12.0",
+            "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
+            "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
+            "dev": true
+        },
+        "node_modules/brace-expansion": {
+            "version": "1.1.11",
+            "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+            "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+            "dev": true,
+            "dependencies": {
+                "balanced-match": "^1.0.0",
+                "concat-map": "0.0.1"
+            }
+        },
+        "node_modules/braces": {
+            "version": "3.0.2",
+            "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+            "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+            "dev": true,
+            "dependencies": {
+                "fill-range": "^7.0.1"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/brorand": {
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
+            "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=",
+            "dev": true
+        },
+        "node_modules/browser-headers": {
+            "version": "0.4.1",
+            "resolved": "https://registry.npmjs.org/browser-headers/-/browser-headers-0.4.1.tgz",
+            "integrity": "sha512-CA9hsySZVo9371qEHjHZtYxV2cFtVj5Wj/ZHi8ooEsrtm4vOnl9Y9HmyYWk9q+05d7K3rdoAE0j3MVEFVvtQtg==",
+            "peer": true
+        },
+        "node_modules/browserslist": {
+            "version": "4.17.0",
+            "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.17.0.tgz",
+            "integrity": "sha512-g2BJ2a0nEYvEFQC208q8mVAhfNwpZ5Mu8BwgtCdZKO3qx98HChmeg448fPdUzld8aFmfLgVh7yymqV+q1lJZ5g==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "caniuse-lite": "^1.0.30001254",
+                "colorette": "^1.3.0",
+                "electron-to-chromium": "^1.3.830",
+                "escalade": "^3.1.1",
+                "node-releases": "^1.1.75"
+            },
+            "bin": {
+                "browserslist": "cli.js"
+            },
+            "engines": {
+                "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+            },
+            "funding": {
+                "type": "opencollective",
+                "url": "https://opencollective.com/browserslist"
+            }
+        },
+        "node_modules/buffer-from": {
+            "version": "1.1.2",
+            "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
+            "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
+            "dev": true,
+            "peer": true
+        },
+        "node_modules/bufferutil": {
+            "version": "4.0.3",
+            "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.3.tgz",
+            "integrity": "sha512-yEYTwGndELGvfXsImMBLop58eaGW+YdONi1fNjTINSY98tmMmFijBG6WXgdkfuLNt4imzQNtIE+eBp1PVpMCSw==",
+            "dev": true,
+            "hasInstallScript": true,
+            "optional": true,
+            "peer": true,
+            "dependencies": {
+                "node-gyp-build": "^4.2.0"
+            }
+        },
+        "node_modules/builtin-modules": {
+            "version": "1.1.1",
+            "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz",
+            "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=",
+            "dev": true,
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
+        "node_modules/caniuse-lite": {
+            "version": "1.0.30001257",
+            "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001257.tgz",
+            "integrity": "sha512-JN49KplOgHSXpIsVSF+LUyhD8PUp6xPpAXeRrrcBh4KBeP7W864jHn6RvzJgDlrReyeVjMFJL3PLpPvKIxlIHA==",
+            "dev": true,
+            "peer": true,
+            "funding": {
+                "type": "opencollective",
+                "url": "https://opencollective.com/browserslist"
+            }
+        },
+        "node_modules/chalk": {
+            "version": "2.4.2",
+            "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+            "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+            "dev": true,
+            "dependencies": {
+                "ansi-styles": "^3.2.1",
+                "escape-string-regexp": "^1.0.5",
+                "supports-color": "^5.3.0"
+            },
+            "engines": {
+                "node": ">=4"
+            }
+        },
+        "node_modules/chrome-trace-event": {
+            "version": "1.0.3",
+            "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz",
+            "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==",
+            "dev": true,
+            "peer": true,
+            "engines": {
+                "node": ">=6.0"
+            }
+        },
+        "node_modules/color-convert": {
+            "version": "1.9.3",
+            "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+            "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+            "dev": true,
+            "dependencies": {
+                "color-name": "1.1.3"
+            }
+        },
+        "node_modules/color-name": {
+            "version": "1.1.3",
+            "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+            "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+            "dev": true
+        },
+        "node_modules/colorette": {
+            "version": "1.4.0",
+            "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz",
+            "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==",
+            "dev": true,
+            "peer": true
+        },
+        "node_modules/command-line-args": {
+            "version": "4.0.7",
+            "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-4.0.7.tgz",
+            "integrity": "sha512-aUdPvQRAyBvQd2n7jXcsMDz68ckBJELXNzBybCHOibUWEg0mWTnaYCSRU8h9R+aNRSvDihJtssSRCiDRpLaezA==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "array-back": "^2.0.0",
+                "find-replace": "^1.0.3",
+                "typical": "^2.6.1"
+            },
+            "bin": {
+                "command-line-args": "bin/cli.js"
+            }
+        },
+        "node_modules/commander": {
+            "version": "2.20.3",
+            "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+            "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+            "dev": true
+        },
+        "node_modules/concat-map": {
+            "version": "0.0.1",
+            "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+            "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+            "dev": true
+        },
+        "node_modules/copy-dir": {
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/copy-dir/-/copy-dir-1.3.0.tgz",
+            "integrity": "sha512-Q4+qBFnN4bwGwvtXXzbp4P/4iNk0MaiGAzvQ8OiMtlLjkIKjmNN689uVzShSM0908q7GoFHXIPx4zi75ocoaHw==",
+            "dev": true
+        },
+        "node_modules/dataloader": {
+            "version": "1.4.0",
+            "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-1.4.0.tgz",
+            "integrity": "sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw=="
+        },
+        "node_modules/debug": {
+            "version": "4.3.2",
+            "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz",
+            "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "ms": "2.1.2"
+            },
+            "engines": {
+                "node": ">=6.0"
+            },
+            "peerDependenciesMeta": {
+                "supports-color": {
+                    "optional": true
+                }
+            }
+        },
+        "node_modules/diff": {
+            "version": "4.0.2",
+            "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
+            "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
+            "dev": true,
+            "engines": {
+                "node": ">=0.3.1"
+            }
+        },
+        "node_modules/electron-to-chromium": {
+            "version": "1.3.836",
+            "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.836.tgz",
+            "integrity": "sha512-Ney3pHOJBWkG/AqYjrW0hr2AUCsao+2uvq9HUlRP8OlpSdk/zOHOUJP7eu0icDvePC9DlgffuelP4TnOJmMRUg==",
+            "dev": true,
+            "peer": true
+        },
+        "node_modules/elliptic": {
+            "version": "6.5.4",
+            "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz",
+            "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==",
+            "dev": true,
+            "dependencies": {
+                "bn.js": "^4.11.9",
+                "brorand": "^1.1.0",
+                "hash.js": "^1.0.0",
+                "hmac-drbg": "^1.0.1",
+                "inherits": "^2.0.4",
+                "minimalistic-assert": "^1.0.1",
+                "minimalistic-crypto-utils": "^1.0.1"
+            }
+        },
+        "node_modules/enhanced-resolve": {
+            "version": "5.8.2",
+            "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.8.2.tgz",
+            "integrity": "sha512-F27oB3WuHDzvR2DOGNTaYy0D5o0cnrv8TeI482VM4kYgQd/FT9lUQwuNsJ0oOHtBUq7eiW5ytqzp7nBFknL+GA==",
+            "dev": true,
+            "dependencies": {
+                "graceful-fs": "^4.2.4",
+                "tapable": "^2.2.0"
+            },
+            "engines": {
+                "node": ">=10.13.0"
+            }
+        },
+        "node_modules/es-module-lexer": {
+            "version": "0.7.1",
+            "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.7.1.tgz",
+            "integrity": "sha512-MgtWFl5No+4S3TmhDmCz2ObFGm6lEpTnzbQi+Dd+pw4mlTIZTmM2iAs5gRlmx5zS9luzobCSBSI90JM/1/JgOw==",
+            "dev": true,
+            "peer": true
+        },
+        "node_modules/escalade": {
+            "version": "3.1.1",
+            "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
+            "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
+            "dev": true,
+            "peer": true,
+            "engines": {
+                "node": ">=6"
+            }
+        },
+        "node_modules/escape-string-regexp": {
+            "version": "1.0.5",
+            "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+            "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+            "dev": true,
+            "engines": {
+                "node": ">=0.8.0"
+            }
+        },
+        "node_modules/eslint-scope": {
+            "version": "5.1.1",
+            "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
+            "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "esrecurse": "^4.3.0",
+                "estraverse": "^4.1.1"
+            },
+            "engines": {
+                "node": ">=8.0.0"
+            }
+        },
+        "node_modules/esm": {
+            "version": "3.2.25",
+            "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz",
+            "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==",
+            "dev": true,
+            "engines": {
+                "node": ">=6"
+            }
+        },
+        "node_modules/esprima": {
+            "version": "4.0.1",
+            "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+            "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+            "dev": true,
+            "bin": {
+                "esparse": "bin/esparse.js",
+                "esvalidate": "bin/esvalidate.js"
+            },
+            "engines": {
+                "node": ">=4"
+            }
+        },
+        "node_modules/esrecurse": {
+            "version": "4.3.0",
+            "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+            "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "estraverse": "^5.2.0"
+            },
+            "engines": {
+                "node": ">=4.0"
+            }
+        },
+        "node_modules/esrecurse/node_modules/estraverse": {
+            "version": "5.2.0",
+            "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz",
+            "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==",
+            "dev": true,
+            "peer": true,
+            "engines": {
+                "node": ">=4.0"
+            }
+        },
+        "node_modules/estraverse": {
+            "version": "4.3.0",
+            "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
+            "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
+            "dev": true,
+            "peer": true,
+            "engines": {
+                "node": ">=4.0"
+            }
+        },
+        "node_modules/ethers": {
+            "version": "5.4.7",
+            "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.4.7.tgz",
+            "integrity": "sha512-iZc5p2nqfWK1sj8RabwsPM28cr37Bpq7ehTQ5rWExBr2Y09Sn1lDKZOED26n+TsZMye7Y6mIgQ/1cwpSD8XZew==",
+            "dev": true,
+            "funding": [
+                {
+                    "type": "individual",
+                    "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+                },
+                {
+                    "type": "individual",
+                    "url": "https://www.buymeacoffee.com/ricmoo"
+                }
+            ],
+            "dependencies": {
+                "@ethersproject/abi": "5.4.1",
+                "@ethersproject/abstract-provider": "5.4.1",
+                "@ethersproject/abstract-signer": "5.4.1",
+                "@ethersproject/address": "5.4.0",
+                "@ethersproject/base64": "5.4.0",
+                "@ethersproject/basex": "5.4.0",
+                "@ethersproject/bignumber": "5.4.2",
+                "@ethersproject/bytes": "5.4.0",
+                "@ethersproject/constants": "5.4.0",
+                "@ethersproject/contracts": "5.4.1",
+                "@ethersproject/hash": "5.4.0",
+                "@ethersproject/hdnode": "5.4.0",
+                "@ethersproject/json-wallets": "5.4.0",
+                "@ethersproject/keccak256": "5.4.0",
+                "@ethersproject/logger": "5.4.1",
+                "@ethersproject/networks": "5.4.2",
+                "@ethersproject/pbkdf2": "5.4.0",
+                "@ethersproject/properties": "5.4.1",
+                "@ethersproject/providers": "5.4.5",
+                "@ethersproject/random": "5.4.0",
+                "@ethersproject/rlp": "5.4.0",
+                "@ethersproject/sha2": "5.4.0",
+                "@ethersproject/signing-key": "5.4.0",
+                "@ethersproject/solidity": "5.4.0",
+                "@ethersproject/strings": "5.4.0",
+                "@ethersproject/transactions": "5.4.0",
+                "@ethersproject/units": "5.4.0",
+                "@ethersproject/wallet": "5.4.0",
+                "@ethersproject/web": "5.4.0",
+                "@ethersproject/wordlists": "5.4.0"
+            }
+        },
+        "node_modules/events": {
+            "version": "3.3.0",
+            "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
+            "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
+            "dev": true,
+            "peer": true,
+            "engines": {
+                "node": ">=0.8.x"
+            }
+        },
+        "node_modules/fast-deep-equal": {
+            "version": "3.1.3",
+            "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+            "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+            "dev": true,
+            "peer": true
+        },
+        "node_modules/fast-json-stable-stringify": {
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+            "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+            "dev": true,
+            "peer": true
+        },
+        "node_modules/fill-range": {
+            "version": "7.0.1",
+            "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+            "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+            "dev": true,
+            "dependencies": {
+                "to-regex-range": "^5.0.1"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/find": {
+            "version": "0.3.0",
+            "resolved": "https://registry.npmjs.org/find/-/find-0.3.0.tgz",
+            "integrity": "sha512-iSd+O4OEYV/I36Zl8MdYJO0xD82wH528SaCieTVHhclgiYNe9y+yPKSwK+A7/WsmHL1EZ+pYUJBXWTL5qofksw==",
+            "dev": true,
+            "dependencies": {
+                "traverse-chain": "~0.1.0"
+            }
+        },
+        "node_modules/find-replace": {
+            "version": "1.0.3",
+            "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-1.0.3.tgz",
+            "integrity": "sha1-uI5zZNLZyVlVnziMZmcNYTBEH6A=",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "array-back": "^1.0.4",
+                "test-value": "^2.1.0"
+            },
+            "engines": {
+                "node": ">=4.0.0"
+            }
+        },
+        "node_modules/find-replace/node_modules/array-back": {
+            "version": "1.0.4",
+            "resolved": "https://registry.npmjs.org/array-back/-/array-back-1.0.4.tgz",
+            "integrity": "sha1-ZEun8JX3/898Q7Xw3DnTwfA8Bjs=",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "typical": "^2.6.0"
+            },
+            "engines": {
+                "node": ">=0.12.0"
+            }
+        },
+        "node_modules/fs-extra": {
+            "version": "7.0.1",
+            "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
+            "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "graceful-fs": "^4.1.2",
+                "jsonfile": "^4.0.0",
+                "universalify": "^0.1.0"
+            },
+            "engines": {
+                "node": ">=6 <7 || >=8"
+            }
+        },
+        "node_modules/fs.realpath": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+            "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+            "dev": true
+        },
+        "node_modules/function-bind": {
+            "version": "1.1.1",
+            "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+            "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+            "dev": true
+        },
+        "node_modules/glob": {
+            "version": "7.1.7",
+            "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz",
+            "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==",
+            "dev": true,
+            "dependencies": {
+                "fs.realpath": "^1.0.0",
+                "inflight": "^1.0.4",
+                "inherits": "2",
+                "minimatch": "^3.0.4",
+                "once": "^1.3.0",
+                "path-is-absolute": "^1.0.0"
+            },
+            "engines": {
+                "node": "*"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/isaacs"
+            }
+        },
+        "node_modules/glob-to-regexp": {
+            "version": "0.4.1",
+            "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
+            "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
+            "dev": true,
+            "peer": true
+        },
+        "node_modules/google-protobuf": {
+            "version": "3.18.0",
+            "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.18.0.tgz",
+            "integrity": "sha512-WlaQWRkUOo/lm9uTgNH6nk9IQt814RggWPzKBfnAVewOFzSzRUSmS1yUWRT6ixW1vS7er5p6tmLSmwzpPpmc8A==",
+            "peer": true
+        },
+        "node_modules/graceful-fs": {
+            "version": "4.2.8",
+            "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz",
+            "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==",
+            "dev": true
+        },
+        "node_modules/has": {
+            "version": "1.0.3",
+            "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+            "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+            "dev": true,
+            "dependencies": {
+                "function-bind": "^1.1.1"
+            },
+            "engines": {
+                "node": ">= 0.4.0"
+            }
+        },
+        "node_modules/has-flag": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+            "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+            "dev": true,
+            "engines": {
+                "node": ">=4"
+            }
+        },
+        "node_modules/hash.js": {
+            "version": "1.1.7",
+            "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
+            "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
+            "dev": true,
+            "dependencies": {
+                "inherits": "^2.0.3",
+                "minimalistic-assert": "^1.0.1"
+            }
+        },
+        "node_modules/hmac-drbg": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
+            "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
+            "dev": true,
+            "dependencies": {
+                "hash.js": "^1.0.3",
+                "minimalistic-assert": "^1.0.0",
+                "minimalistic-crypto-utils": "^1.0.1"
+            }
+        },
+        "node_modules/inflight": {
+            "version": "1.0.6",
+            "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+            "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+            "dev": true,
+            "dependencies": {
+                "once": "^1.3.0",
+                "wrappy": "1"
+            }
+        },
+        "node_modules/inherits": {
+            "version": "2.0.4",
+            "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+            "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+            "dev": true
+        },
+        "node_modules/is-core-module": {
+            "version": "2.6.0",
+            "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.6.0.tgz",
+            "integrity": "sha512-wShG8vs60jKfPWpF2KZRaAtvt3a20OAn7+IJ6hLPECpSABLcKtFKTTI4ZtH5QcBruBHlq+WsdHWyz0BCZW7svQ==",
+            "dev": true,
+            "dependencies": {
+                "has": "^1.0.3"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/is-number": {
+            "version": "7.0.0",
+            "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+            "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+            "dev": true,
+            "engines": {
+                "node": ">=0.12.0"
+            }
+        },
+        "node_modules/jest-worker": {
+            "version": "27.2.0",
+            "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.2.0.tgz",
+            "integrity": "sha512-laB0ZVIBz+voh/QQy9dmUuuDsadixeerrKqyVpgPz+CCWiOYjOBabUXHIXZhsdvkWbLqSHbgkAHWl5cg24Q6RA==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "@types/node": "*",
+                "merge-stream": "^2.0.0",
+                "supports-color": "^8.0.0"
+            },
+            "engines": {
+                "node": ">= 10.13.0"
+            }
+        },
+        "node_modules/jest-worker/node_modules/has-flag": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+            "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+            "dev": true,
+            "peer": true,
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/jest-worker/node_modules/supports-color": {
+            "version": "8.1.1",
+            "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+            "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "has-flag": "^4.0.0"
+            },
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/chalk/supports-color?sponsor=1"
+            }
+        },
+        "node_modules/js-sha3": {
+            "version": "0.5.7",
+            "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.7.tgz",
+            "integrity": "sha1-DU/9gALVMzqrr0oj7tL2N0yfKOc=",
+            "dev": true
+        },
+        "node_modules/js-tokens": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+            "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+            "dev": true
+        },
+        "node_modules/js-yaml": {
+            "version": "3.14.1",
+            "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
+            "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
+            "dev": true,
+            "dependencies": {
+                "argparse": "^1.0.7",
+                "esprima": "^4.0.0"
+            },
+            "bin": {
+                "js-yaml": "bin/js-yaml.js"
+            }
+        },
+        "node_modules/json-parse-better-errors": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
+            "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
+            "dev": true,
+            "peer": true
+        },
+        "node_modules/json-schema-traverse": {
+            "version": "0.4.1",
+            "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+            "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+            "dev": true,
+            "peer": true
+        },
+        "node_modules/jsonfile": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
+            "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
+            "dev": true,
+            "peer": true,
+            "optionalDependencies": {
+                "graceful-fs": "^4.1.6"
+            }
+        },
+        "node_modules/loader-runner": {
+            "version": "4.2.0",
+            "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz",
+            "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==",
+            "dev": true,
+            "peer": true,
+            "engines": {
+                "node": ">=6.11.5"
+            }
+        },
+        "node_modules/lodash": {
+            "version": "4.17.21",
+            "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+            "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+        },
+        "node_modules/long": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
+            "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="
+        },
+        "node_modules/lru-cache": {
+            "version": "6.0.0",
+            "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+            "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+            "dev": true,
+            "dependencies": {
+                "yallist": "^4.0.0"
+            },
+            "engines": {
+                "node": ">=10"
+            }
+        },
+        "node_modules/merge-stream": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+            "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+            "dev": true,
+            "peer": true
+        },
+        "node_modules/micromatch": {
+            "version": "4.0.4",
+            "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
+            "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==",
+            "dev": true,
+            "dependencies": {
+                "braces": "^3.0.1",
+                "picomatch": "^2.2.3"
+            },
+            "engines": {
+                "node": ">=8.6"
+            }
+        },
+        "node_modules/mime-db": {
+            "version": "1.49.0",
+            "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz",
+            "integrity": "sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==",
+            "dev": true,
+            "peer": true,
+            "engines": {
+                "node": ">= 0.6"
+            }
+        },
+        "node_modules/mime-types": {
+            "version": "2.1.32",
+            "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.32.tgz",
+            "integrity": "sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "mime-db": "1.49.0"
+            },
+            "engines": {
+                "node": ">= 0.6"
+            }
+        },
+        "node_modules/minimalistic-assert": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
+            "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
+            "dev": true
+        },
+        "node_modules/minimalistic-crypto-utils": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
+            "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=",
+            "dev": true
+        },
+        "node_modules/minimatch": {
+            "version": "3.0.4",
+            "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+            "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+            "dev": true,
+            "dependencies": {
+                "brace-expansion": "^1.1.7"
+            },
+            "engines": {
+                "node": "*"
+            }
+        },
+        "node_modules/minimist": {
+            "version": "1.2.5",
+            "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
+            "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
+            "dev": true
+        },
+        "node_modules/mkdirp": {
+            "version": "0.5.5",
+            "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
+            "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
+            "dev": true,
+            "dependencies": {
+                "minimist": "^1.2.5"
+            },
+            "bin": {
+                "mkdirp": "bin/cmd.js"
+            }
+        },
+        "node_modules/ms": {
+            "version": "2.1.2",
+            "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+            "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+            "dev": true,
+            "peer": true
+        },
+        "node_modules/neo-async": {
+            "version": "2.6.2",
+            "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
+            "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
+            "dev": true,
+            "peer": true
+        },
+        "node_modules/node-gyp-build": {
+            "version": "4.2.3",
+            "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.3.tgz",
+            "integrity": "sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg==",
+            "dev": true,
+            "optional": true,
+            "peer": true,
+            "bin": {
+                "node-gyp-build": "bin.js",
+                "node-gyp-build-optional": "optional.js",
+                "node-gyp-build-test": "build-test.js"
+            }
+        },
+        "node_modules/node-releases": {
+            "version": "1.1.75",
+            "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.75.tgz",
+            "integrity": "sha512-Qe5OUajvqrqDSy6wrWFmMwfJ0jVgwiw4T3KqmbTcZ62qW0gQkheXYhcFM1+lOVcGUoRxcEcfyvFMAnDgaF1VWw==",
+            "dev": true,
+            "peer": true
+        },
+        "node_modules/object-hash": {
+            "version": "1.3.1",
+            "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-1.3.1.tgz",
+            "integrity": "sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA==",
+            "engines": {
+                "node": ">= 0.10.0"
+            }
+        },
+        "node_modules/once": {
+            "version": "1.4.0",
+            "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+            "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+            "dev": true,
+            "dependencies": {
+                "wrappy": "1"
+            }
+        },
+        "node_modules/p-limit": {
+            "version": "3.1.0",
+            "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+            "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "yocto-queue": "^0.1.0"
+            },
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
+        "node_modules/path-is-absolute": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+            "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+            "dev": true,
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
+        "node_modules/path-parse": {
+            "version": "1.0.7",
+            "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+            "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+            "dev": true
+        },
+        "node_modules/picomatch": {
+            "version": "2.3.0",
+            "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz",
+            "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==",
+            "dev": true,
+            "engines": {
+                "node": ">=8.6"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/jonschlinkert"
+            }
+        },
+        "node_modules/prettier": {
+            "version": "2.4.0",
+            "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.4.0.tgz",
+            "integrity": "sha512-DsEPLY1dE5HF3BxCRBmD4uYZ+5DCbvatnolqTqcxEgKVZnL2kUfyu7b8pPQ5+hTBkdhU9SLUmK0/pHb07RE4WQ==",
+            "bin": {
+                "prettier": "bin-prettier.js"
+            },
+            "engines": {
+                "node": ">=10.13.0"
+            }
+        },
+        "node_modules/protobufjs": {
+            "version": "6.11.2",
+            "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.2.tgz",
+            "integrity": "sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw==",
+            "hasInstallScript": true,
+            "dependencies": {
+                "@protobufjs/aspromise": "^1.1.2",
+                "@protobufjs/base64": "^1.1.2",
+                "@protobufjs/codegen": "^2.0.4",
+                "@protobufjs/eventemitter": "^1.1.0",
+                "@protobufjs/fetch": "^1.1.0",
+                "@protobufjs/float": "^1.0.2",
+                "@protobufjs/inquire": "^1.1.0",
+                "@protobufjs/path": "^1.1.2",
+                "@protobufjs/pool": "^1.1.0",
+                "@protobufjs/utf8": "^1.1.0",
+                "@types/long": "^4.0.1",
+                "@types/node": ">=13.7.0",
+                "long": "^4.0.0"
+            },
+            "bin": {
+                "pbjs": "bin/pbjs",
+                "pbts": "bin/pbts"
+            }
+        },
+        "node_modules/punycode": {
+            "version": "2.1.1",
+            "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+            "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
+            "dev": true,
+            "peer": true,
+            "engines": {
+                "node": ">=6"
+            }
+        },
+        "node_modules/randombytes": {
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
+            "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "safe-buffer": "^5.1.0"
+            }
+        },
+        "node_modules/resolve": {
+            "version": "1.20.0",
+            "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz",
+            "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==",
+            "dev": true,
+            "dependencies": {
+                "is-core-module": "^2.2.0",
+                "path-parse": "^1.0.6"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/safe-buffer": {
+            "version": "5.2.1",
+            "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+            "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+            "dev": true,
+            "funding": [
+                {
+                    "type": "github",
+                    "url": "https://github.com/sponsors/feross"
+                },
+                {
+                    "type": "patreon",
+                    "url": "https://www.patreon.com/feross"
+                },
+                {
+                    "type": "consulting",
+                    "url": "https://feross.org/support"
+                }
+            ],
+            "peer": true
+        },
+        "node_modules/schema-utils": {
+            "version": "3.1.1",
+            "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
+            "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "@types/json-schema": "^7.0.8",
+                "ajv": "^6.12.5",
+                "ajv-keywords": "^3.5.2"
+            },
+            "engines": {
+                "node": ">= 10.13.0"
+            },
+            "funding": {
+                "type": "opencollective",
+                "url": "https://opencollective.com/webpack"
+            }
+        },
+        "node_modules/scrypt-js": {
+            "version": "3.0.1",
+            "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz",
+            "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==",
+            "dev": true
+        },
+        "node_modules/semver": {
+            "version": "5.7.1",
+            "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+            "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+            "dev": true,
+            "bin": {
+                "semver": "bin/semver"
+            }
+        },
+        "node_modules/serialize-javascript": {
+            "version": "6.0.0",
+            "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz",
+            "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "randombytes": "^2.1.0"
+            }
+        },
+        "node_modules/source-map-support": {
+            "version": "0.5.20",
+            "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.20.tgz",
+            "integrity": "sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "buffer-from": "^1.0.0",
+                "source-map": "^0.6.0"
+            }
+        },
+        "node_modules/source-map-support/node_modules/source-map": {
+            "version": "0.6.1",
+            "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+            "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+            "dev": true,
+            "peer": true,
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
+        "node_modules/sprintf-js": {
+            "version": "1.0.3",
+            "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+            "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
+            "dev": true
+        },
+        "node_modules/supports-color": {
+            "version": "5.5.0",
+            "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+            "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+            "dev": true,
+            "dependencies": {
+                "has-flag": "^3.0.0"
+            },
+            "engines": {
+                "node": ">=4"
+            }
+        },
+        "node_modules/tapable": {
+            "version": "2.2.1",
+            "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
+            "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
+            "dev": true,
+            "engines": {
+                "node": ">=6"
+            }
+        },
+        "node_modules/terser": {
+            "version": "5.8.0",
+            "resolved": "https://registry.npmjs.org/terser/-/terser-5.8.0.tgz",
+            "integrity": "sha512-f0JH+6yMpneYcRJN314lZrSwu9eKkUFEHLN/kNy8ceh8gaRiLgFPJqrB9HsXjhEGdv4e/ekjTOFxIlL6xlma8A==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "commander": "^2.20.0",
+                "source-map": "~0.7.2",
+                "source-map-support": "~0.5.20"
+            },
+            "bin": {
+                "terser": "bin/terser"
+            },
+            "engines": {
+                "node": ">=10"
+            }
+        },
+        "node_modules/terser-webpack-plugin": {
+            "version": "5.2.4",
+            "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.2.4.tgz",
+            "integrity": "sha512-E2CkNMN+1cho04YpdANyRrn8CyN4yMy+WdFKZIySFZrGXZxJwJP6PMNGGc/Mcr6qygQHUUqRxnAPmi0M9f00XA==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "jest-worker": "^27.0.6",
+                "p-limit": "^3.1.0",
+                "schema-utils": "^3.1.1",
+                "serialize-javascript": "^6.0.0",
+                "source-map": "^0.6.1",
+                "terser": "^5.7.2"
+            },
+            "engines": {
+                "node": ">= 10.13.0"
+            },
+            "funding": {
+                "type": "opencollective",
+                "url": "https://opencollective.com/webpack"
+            },
+            "peerDependencies": {
+                "webpack": "^5.1.0"
+            },
+            "peerDependenciesMeta": {
+                "@swc/core": {
+                    "optional": true
+                },
+                "esbuild": {
+                    "optional": true
+                },
+                "uglify-js": {
+                    "optional": true
+                }
+            }
+        },
+        "node_modules/terser-webpack-plugin/node_modules/source-map": {
+            "version": "0.6.1",
+            "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+            "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+            "dev": true,
+            "peer": true,
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
+        "node_modules/terser/node_modules/source-map": {
+            "version": "0.7.3",
+            "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
+            "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
+            "dev": true,
+            "peer": true,
+            "engines": {
+                "node": ">= 8"
+            }
+        },
+        "node_modules/test-value": {
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/test-value/-/test-value-2.1.0.tgz",
+            "integrity": "sha1-Edpv9nDzRxpztiXKTz/c97t0gpE=",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "array-back": "^1.0.3",
+                "typical": "^2.6.0"
+            },
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
+        "node_modules/test-value/node_modules/array-back": {
+            "version": "1.0.4",
+            "resolved": "https://registry.npmjs.org/array-back/-/array-back-1.0.4.tgz",
+            "integrity": "sha1-ZEun8JX3/898Q7Xw3DnTwfA8Bjs=",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "typical": "^2.6.0"
+            },
+            "engines": {
+                "node": ">=0.12.0"
+            }
+        },
+        "node_modules/to-regex-range": {
+            "version": "5.0.1",
+            "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+            "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+            "dev": true,
+            "dependencies": {
+                "is-number": "^7.0.0"
+            },
+            "engines": {
+                "node": ">=8.0"
+            }
+        },
+        "node_modules/traverse-chain": {
+            "version": "0.1.0",
+            "resolved": "https://registry.npmjs.org/traverse-chain/-/traverse-chain-0.1.0.tgz",
+            "integrity": "sha1-YdvC1Ttp/2CRoSoWj9fUMxB+QPE=",
+            "dev": true
+        },
+        "node_modules/ts-essentials": {
+            "version": "7.0.3",
+            "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-7.0.3.tgz",
+            "integrity": "sha512-8+gr5+lqO3G84KdiTSMRLtuyJ+nTBVRKuCrK4lidMPdVeEp0uqC875uE5NMcaA7YYMN7XsNiFQuMvasF8HT/xQ==",
+            "dev": true,
+            "peerDependencies": {
+                "typescript": ">=3.7.0"
+            }
+        },
+        "node_modules/ts-loader": {
+            "version": "9.2.5",
+            "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.2.5.tgz",
+            "integrity": "sha512-al/ATFEffybdRMUIr5zMEWQdVnCGMUA9d3fXJ8dBVvBlzytPvIszoG9kZoR+94k6/i293RnVOXwMaWbXhNy9pQ==",
+            "dev": true,
+            "dependencies": {
+                "chalk": "^4.1.0",
+                "enhanced-resolve": "^5.0.0",
+                "micromatch": "^4.0.0",
+                "semver": "^7.3.4"
+            },
+            "engines": {
+                "node": ">=12.0.0"
+            },
+            "peerDependencies": {
+                "typescript": "*",
+                "webpack": "^5.0.0"
+            }
+        },
+        "node_modules/ts-loader/node_modules/ansi-styles": {
+            "version": "4.3.0",
+            "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+            "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+            "dev": true,
+            "dependencies": {
+                "color-convert": "^2.0.1"
+            },
+            "engines": {
+                "node": ">=8"
+            },
+            "funding": {
+                "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+            }
+        },
+        "node_modules/ts-loader/node_modules/chalk": {
+            "version": "4.1.2",
+            "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+            "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+            "dev": true,
+            "dependencies": {
+                "ansi-styles": "^4.1.0",
+                "supports-color": "^7.1.0"
+            },
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/chalk/chalk?sponsor=1"
+            }
+        },
+        "node_modules/ts-loader/node_modules/color-convert": {
+            "version": "2.0.1",
+            "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+            "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+            "dev": true,
+            "dependencies": {
+                "color-name": "~1.1.4"
+            },
+            "engines": {
+                "node": ">=7.0.0"
+            }
+        },
+        "node_modules/ts-loader/node_modules/color-name": {
+            "version": "1.1.4",
+            "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+            "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+            "dev": true
+        },
+        "node_modules/ts-loader/node_modules/has-flag": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+            "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+            "dev": true,
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/ts-loader/node_modules/semver": {
+            "version": "7.3.5",
+            "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
+            "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
+            "dev": true,
+            "dependencies": {
+                "lru-cache": "^6.0.0"
+            },
+            "bin": {
+                "semver": "bin/semver.js"
+            },
+            "engines": {
+                "node": ">=10"
+            }
+        },
+        "node_modules/ts-loader/node_modules/supports-color": {
+            "version": "7.2.0",
+            "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+            "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+            "dev": true,
+            "dependencies": {
+                "has-flag": "^4.0.0"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/ts-poet": {
+            "version": "4.5.0",
+            "resolved": "https://registry.npmjs.org/ts-poet/-/ts-poet-4.5.0.tgz",
+            "integrity": "sha512-Vs2Zsiz3zf5qdFulFTIEpaLdgWeHXKh+4pv+ycVqEh+ZuUOVGrN0i9lbxVx7DB1FBogExytz3OuaBMJfWffpSQ==",
+            "dependencies": {
+                "@types/prettier": "^1.19.0",
+                "lodash": "^4.17.15",
+                "prettier": "^2.0.2"
+            }
+        },
+        "node_modules/ts-poet/node_modules/@types/prettier": {
+            "version": "1.19.1",
+            "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-1.19.1.tgz",
+            "integrity": "sha512-5qOlnZscTn4xxM5MeGXAMOsIOIKIbh9e85zJWfBRVPlRMEVawzoPhINYbRGkBZCI8LxvBe7tJCdWiarA99OZfQ=="
+        },
+        "node_modules/ts-proto": {
+            "version": "1.83.1",
+            "resolved": "https://registry.npmjs.org/ts-proto/-/ts-proto-1.83.1.tgz",
+            "integrity": "sha512-pAB7FKIqMKjTnakvMyBB7VeIeXPl+3YPWGfp03laSRf7tGTtrt8xV9jyrLB1WU5vSzusYbz57kBCt4lqbZULqw==",
+            "dependencies": {
+                "@types/object-hash": "^1.3.0",
+                "dataloader": "^1.4.0",
+                "object-hash": "^1.3.1",
+                "protobufjs": "^6.8.8",
+                "ts-poet": "^4.5.0",
+                "ts-proto-descriptors": "^1.2.1"
+            },
+            "bin": {
+                "protoc-gen-ts_proto": "protoc-gen-ts_proto"
+            }
+        },
+        "node_modules/ts-proto-descriptors": {
+            "version": "1.3.1",
+            "resolved": "https://registry.npmjs.org/ts-proto-descriptors/-/ts-proto-descriptors-1.3.1.tgz",
+            "integrity": "sha512-Cybb3fqceMwA6JzHdC32dIo8eVGVmXrM6TWhdk1XQVVHT/6OQqk0ioyX1dIdu3rCIBhRmWUhUE4HsyK+olmgMw==",
+            "dependencies": {
+                "long": "^4.0.0",
+                "protobufjs": "^6.8.8"
+            }
+        },
+        "node_modules/tslib": {
+            "version": "1.14.1",
+            "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+            "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
+            "dev": true
+        },
+        "node_modules/tslint": {
+            "version": "6.1.3",
+            "resolved": "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz",
+            "integrity": "sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg==",
+            "deprecated": "TSLint has been deprecated in favor of ESLint. Please see https://github.com/palantir/tslint/issues/4534 for more information.",
+            "dev": true,
+            "dependencies": {
+                "@babel/code-frame": "^7.0.0",
+                "builtin-modules": "^1.1.1",
+                "chalk": "^2.3.0",
+                "commander": "^2.12.1",
+                "diff": "^4.0.1",
+                "glob": "^7.1.1",
+                "js-yaml": "^3.13.1",
+                "minimatch": "^3.0.4",
+                "mkdirp": "^0.5.3",
+                "resolve": "^1.3.2",
+                "semver": "^5.3.0",
+                "tslib": "^1.13.0",
+                "tsutils": "^2.29.0"
+            },
+            "bin": {
+                "tslint": "bin/tslint"
+            },
+            "engines": {
+                "node": ">=4.8.0"
+            },
+            "peerDependencies": {
+                "typescript": ">=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >=3.0.0-dev || >= 3.1.0-dev || >= 3.2.0-dev || >= 4.0.0-dev"
+            }
+        },
+        "node_modules/tslint-config-prettier": {
+            "version": "1.18.0",
+            "resolved": "https://registry.npmjs.org/tslint-config-prettier/-/tslint-config-prettier-1.18.0.tgz",
+            "integrity": "sha512-xPw9PgNPLG3iKRxmK7DWr+Ea/SzrvfHtjFt5LBl61gk2UBG/DB9kCXRjv+xyIU1rUtnayLeMUVJBcMX8Z17nDg==",
+            "dev": true,
+            "bin": {
+                "tslint-config-prettier-check": "bin/check.js"
+            },
+            "engines": {
+                "node": ">=4.0.0"
+            }
+        },
+        "node_modules/tsutils": {
+            "version": "2.29.0",
+            "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz",
+            "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==",
+            "dev": true,
+            "dependencies": {
+                "tslib": "^1.8.1"
+            },
+            "peerDependencies": {
+                "typescript": ">=2.1.0 || >=2.1.0-dev || >=2.2.0-dev || >=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >= 3.0.0-dev || >= 3.1.0-dev"
+            }
+        },
+        "node_modules/typechain": {
+            "version": "5.1.2",
+            "resolved": "https://registry.npmjs.org/typechain/-/typechain-5.1.2.tgz",
+            "integrity": "sha512-FuaCxJd7BD3ZAjVJoO+D6TnqKey3pQdsqOBsC83RKYWKli5BDhdf0TPkwfyjt20TUlZvOzJifz+lDwXsRkiSKA==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "@types/prettier": "^2.1.1",
+                "command-line-args": "^4.0.7",
+                "debug": "^4.1.1",
+                "fs-extra": "^7.0.0",
+                "glob": "^7.1.6",
+                "js-sha3": "^0.8.0",
+                "lodash": "^4.17.15",
+                "mkdirp": "^1.0.4",
+                "prettier": "^2.1.2",
+                "ts-essentials": "^7.0.1"
+            },
+            "bin": {
+                "typechain": "dist/cli/cli.js"
+            }
+        },
+        "node_modules/typechain/node_modules/js-sha3": {
+            "version": "0.8.0",
+            "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz",
+            "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==",
+            "dev": true,
+            "peer": true
+        },
+        "node_modules/typechain/node_modules/mkdirp": {
+            "version": "1.0.4",
+            "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
+            "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
+            "dev": true,
+            "peer": true,
+            "bin": {
+                "mkdirp": "bin/cmd.js"
+            },
+            "engines": {
+                "node": ">=10"
+            }
+        },
+        "node_modules/typescript": {
+            "version": "4.4.2",
+            "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.2.tgz",
+            "integrity": "sha512-gzP+t5W4hdy4c+68bfcv0t400HVJMMd2+H9B7gae1nQlBzCqvrXX+6GL/b3GAgyTH966pzrZ70/fRjwAtZksSQ==",
+            "dev": true,
+            "bin": {
+                "tsc": "bin/tsc",
+                "tsserver": "bin/tsserver"
+            },
+            "engines": {
+                "node": ">=4.2.0"
+            }
+        },
+        "node_modules/typical": {
+            "version": "2.6.1",
+            "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz",
+            "integrity": "sha1-XAgOXWYcu+OCWdLnCjxyU+hziB0=",
+            "dev": true,
+            "peer": true
+        },
+        "node_modules/universalify": {
+            "version": "0.1.2",
+            "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
+            "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
+            "dev": true,
+            "peer": true,
+            "engines": {
+                "node": ">= 4.0.0"
+            }
+        },
+        "node_modules/uri-js": {
+            "version": "4.4.1",
+            "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+            "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "punycode": "^2.1.0"
+            }
+        },
+        "node_modules/utf-8-validate": {
+            "version": "5.0.5",
+            "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.5.tgz",
+            "integrity": "sha512-+pnxRYsS/axEpkrrEpzYfNZGXp0IjC/9RIxwM5gntY4Koi8SHmUGSfxfWqxZdRxrtaoVstuOzUp/rbs3JSPELQ==",
+            "dev": true,
+            "hasInstallScript": true,
+            "optional": true,
+            "peer": true,
+            "dependencies": {
+                "node-gyp-build": "^4.2.0"
+            }
+        },
+        "node_modules/watchpack": {
+            "version": "2.2.0",
+            "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.2.0.tgz",
+            "integrity": "sha512-up4YAn/XHgZHIxFBVCdlMiWDj6WaLKpwVeGQk2I5thdYxF/KmF0aaz6TfJZ/hfl1h/XlcDr7k1KH7ThDagpFaA==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "glob-to-regexp": "^0.4.1",
+                "graceful-fs": "^4.1.2"
+            },
+            "engines": {
+                "node": ">=10.13.0"
+            }
+        },
+        "node_modules/webpack": {
+            "version": "5.52.1",
+            "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.52.1.tgz",
+            "integrity": "sha512-wkGb0hLfrS7ML3n2xIKfUIwHbjB6gxwQHyLmVHoAqEQBw+nWo+G6LoHL098FEXqahqximsntjBLuewStrnJk0g==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "@types/eslint-scope": "^3.7.0",
+                "@types/estree": "^0.0.50",
+                "@webassemblyjs/ast": "1.11.1",
+                "@webassemblyjs/wasm-edit": "1.11.1",
+                "@webassemblyjs/wasm-parser": "1.11.1",
+                "acorn": "^8.4.1",
+                "acorn-import-assertions": "^1.7.6",
+                "browserslist": "^4.14.5",
+                "chrome-trace-event": "^1.0.2",
+                "enhanced-resolve": "^5.8.0",
+                "es-module-lexer": "^0.7.1",
+                "eslint-scope": "5.1.1",
+                "events": "^3.2.0",
+                "glob-to-regexp": "^0.4.1",
+                "graceful-fs": "^4.2.4",
+                "json-parse-better-errors": "^1.0.2",
+                "loader-runner": "^4.2.0",
+                "mime-types": "^2.1.27",
+                "neo-async": "^2.6.2",
+                "schema-utils": "^3.1.0",
+                "tapable": "^2.1.1",
+                "terser-webpack-plugin": "^5.1.3",
+                "watchpack": "^2.2.0",
+                "webpack-sources": "^3.2.0"
+            },
+            "bin": {
+                "webpack": "bin/webpack.js"
+            },
+            "engines": {
+                "node": ">=10.13.0"
+            },
+            "funding": {
+                "type": "opencollective",
+                "url": "https://opencollective.com/webpack"
+            },
+            "peerDependenciesMeta": {
+                "webpack-cli": {
+                    "optional": true
+                }
+            }
+        },
+        "node_modules/webpack-sources": {
+            "version": "3.2.0",
+            "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.0.tgz",
+            "integrity": "sha512-fahN08Et7P9trej8xz/Z7eRu8ltyiygEo/hnRi9KqBUs80KeDcnf96ZJo++ewWd84fEf3xSX9bp4ZS9hbw0OBw==",
+            "dev": true,
+            "peer": true,
+            "engines": {
+                "node": ">=10.13.0"
+            }
+        },
+        "node_modules/wrappy": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+            "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+            "dev": true
+        },
+        "node_modules/ws": {
+            "version": "7.4.6",
+            "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz",
+            "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==",
+            "dev": true,
+            "engines": {
+                "node": ">=8.3.0"
+            },
+            "peerDependencies": {
+                "bufferutil": "^4.0.1",
+                "utf-8-validate": "^5.0.2"
+            },
+            "peerDependenciesMeta": {
+                "bufferutil": {
+                    "optional": true
+                },
+                "utf-8-validate": {
+                    "optional": true
+                }
+            }
+        },
+        "node_modules/yallist": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+            "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+            "dev": true
+        },
+        "node_modules/yocto-queue": {
+            "version": "0.1.0",
+            "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+            "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+            "dev": true,
+            "peer": true,
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        }
+    },
+    "dependencies": {
+        "@babel/code-frame": {
+            "version": "7.15.8",
+            "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.15.8.tgz",
+            "integrity": "sha512-2IAnmn8zbvC/jKYhq5Ki9I+DwjlrtMPUCH/CpHvqI4dNnlwHwsxoIhlc8WcYY5LSYknXQtAlFYuHfqAFCvQ4Wg==",
+            "dev": true,
+            "requires": {
+                "@babel/highlight": "^7.14.5"
+            }
+        },
+        "@babel/helper-validator-identifier": {
+            "version": "7.15.7",
+            "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz",
+            "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==",
+            "dev": true
+        },
+        "@babel/highlight": {
+            "version": "7.14.5",
+            "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz",
+            "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==",
+            "dev": true,
+            "requires": {
+                "@babel/helper-validator-identifier": "^7.14.5",
+                "chalk": "^2.0.0",
+                "js-tokens": "^4.0.0"
+            }
+        },
+        "@certusone/p2w-sdk": {
+            "version": "file:../p2w-sdk",
+            "requires": {
+                "@certusone/wormhole-sdk": "file:../../../sdk/js",
+                "@improbable-eng/grpc-web-node-http-transport": "^0.14.1",
+                "@openzeppelin/contracts": "^4.2.0",
+                "@typechain/ethers-v5": "^7.1.2",
+                "@types/long": "^4.0.1",
+                "@types/node": "^16.6.1",
+                "copy-dir": "^1.3.0",
+                "find": "^0.3.0",
+                "prettier": "^2.3.2",
+                "tslint": "^6.1.3",
+                "tslint-config-prettier": "^1.18.0",
+                "typescript": "^4.3.5"
+            }
+        },
+        "@certusone/wormhole-sdk": {
+            "version": "file:../../../sdk/js",
+            "requires": {
+                "@improbable-eng/grpc-web": "^0.14.0",
+                "@openzeppelin/contracts": "^4.2.0",
+                "@solana/spl-token": "^0.1.8",
+                "@solana/web3.js": "^1.24.0",
+                "@terra-money/terra.js": "^2.0.14",
+                "@terra-money/wallet-provider": "^2.2.0",
+                "@typechain/ethers-v5": "^7.0.1",
+                "@types/long": "^4.0.1",
+                "@types/node": "^16.6.1",
+                "@types/react": "^17.0.19",
+                "bech32": "^2.0.0",
+                "copy-dir": "^1.3.0",
+                "ethers": "^5.4.4",
+                "js-base64": "^3.6.1",
+                "prettier": "^2.3.2",
+                "protobufjs": "^6.11.2",
+                "rxjs": "^7.3.0",
+                "tslint": "^6.1.3",
+                "tslint-config-prettier": "^1.18.0",
+                "typescript": "^4.3.5"
+            }
+        },
+        "@ethersproject/abi": {
+            "version": "5.4.1",
+            "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.4.1.tgz",
+            "integrity": "sha512-9mhbjUk76BiSluiiW4BaYyI58KSbDMMQpCLdsAR+RsT2GyATiNYxVv+pGWRrekmsIdY3I+hOqsYQSTkc8L/mcg==",
+            "dev": true,
+            "requires": {
+                "@ethersproject/address": "^5.4.0",
+                "@ethersproject/bignumber": "^5.4.0",
+                "@ethersproject/bytes": "^5.4.0",
+                "@ethersproject/constants": "^5.4.0",
+                "@ethersproject/hash": "^5.4.0",
+                "@ethersproject/keccak256": "^5.4.0",
+                "@ethersproject/logger": "^5.4.0",
+                "@ethersproject/properties": "^5.4.0",
+                "@ethersproject/strings": "^5.4.0"
+            }
+        },
+        "@ethersproject/abstract-provider": {
+            "version": "5.4.1",
+            "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.4.1.tgz",
+            "integrity": "sha512-3EedfKI3LVpjSKgAxoUaI+gB27frKsxzm+r21w9G60Ugk+3wVLQwhi1LsEJAKNV7WoZc8CIpNrATlL1QFABjtQ==",
+            "dev": true,
+            "requires": {
+                "@ethersproject/bignumber": "^5.4.0",
+                "@ethersproject/bytes": "^5.4.0",
+                "@ethersproject/logger": "^5.4.0",
+                "@ethersproject/networks": "^5.4.0",
+                "@ethersproject/properties": "^5.4.0",
+                "@ethersproject/transactions": "^5.4.0",
+                "@ethersproject/web": "^5.4.0"
+            }
+        },
+        "@ethersproject/abstract-signer": {
+            "version": "5.4.1",
+            "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.4.1.tgz",
+            "integrity": "sha512-SkkFL5HVq1k4/25dM+NWP9MILgohJCgGv5xT5AcRruGz4ILpfHeBtO/y6j+Z3UN/PAjDeb4P7E51Yh8wcGNLGA==",
+            "dev": true,
+            "requires": {
+                "@ethersproject/abstract-provider": "^5.4.0",
+                "@ethersproject/bignumber": "^5.4.0",
+                "@ethersproject/bytes": "^5.4.0",
+                "@ethersproject/logger": "^5.4.0",
+                "@ethersproject/properties": "^5.4.0"
+            }
+        },
+        "@ethersproject/address": {
+            "version": "5.4.0",
+            "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.4.0.tgz",
+            "integrity": "sha512-SD0VgOEkcACEG/C6xavlU1Hy3m5DGSXW3CUHkaaEHbAPPsgi0coP5oNPsxau8eTlZOk/bpa/hKeCNoK5IzVI2Q==",
+            "dev": true,
+            "requires": {
+                "@ethersproject/bignumber": "^5.4.0",
+                "@ethersproject/bytes": "^5.4.0",
+                "@ethersproject/keccak256": "^5.4.0",
+                "@ethersproject/logger": "^5.4.0",
+                "@ethersproject/rlp": "^5.4.0"
+            }
+        },
+        "@ethersproject/base64": {
+            "version": "5.4.0",
+            "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.4.0.tgz",
+            "integrity": "sha512-CjQw6E17QDSSC5jiM9YpF7N1aSCHmYGMt9bWD8PWv6YPMxjsys2/Q8xLrROKI3IWJ7sFfZ8B3flKDTM5wlWuZQ==",
+            "dev": true,
+            "requires": {
+                "@ethersproject/bytes": "^5.4.0"
+            }
+        },
+        "@ethersproject/basex": {
+            "version": "5.4.0",
+            "resolved": "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.4.0.tgz",
+            "integrity": "sha512-J07+QCVJ7np2bcpxydFVf/CuYo9mZ7T73Pe7KQY4c1lRlrixMeblauMxHXD0MPwFmUHZIILDNViVkykFBZylbg==",
+            "dev": true,
+            "requires": {
+                "@ethersproject/bytes": "^5.4.0",
+                "@ethersproject/properties": "^5.4.0"
+            }
+        },
+        "@ethersproject/bignumber": {
+            "version": "5.4.2",
+            "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.4.2.tgz",
+            "integrity": "sha512-oIBDhsKy5bs7j36JlaTzFgNPaZjiNDOXsdSgSpXRucUl+UA6L/1YLlFeI3cPAoodcenzF4nxNPV13pcy7XbWjA==",
+            "dev": true,
+            "requires": {
+                "@ethersproject/bytes": "^5.4.0",
+                "@ethersproject/logger": "^5.4.0",
+                "bn.js": "^4.11.9"
+            }
+        },
+        "@ethersproject/bytes": {
+            "version": "5.4.0",
+            "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.4.0.tgz",
+            "integrity": "sha512-H60ceqgTHbhzOj4uRc/83SCN9d+BSUnOkrr2intevqdtEMO1JFVZ1XL84OEZV+QjV36OaZYxtnt4lGmxcGsPfA==",
+            "dev": true,
+            "requires": {
+                "@ethersproject/logger": "^5.4.0"
+            }
+        },
+        "@ethersproject/constants": {
+            "version": "5.4.0",
+            "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.4.0.tgz",
+            "integrity": "sha512-tzjn6S7sj9+DIIeKTJLjK9WGN2Tj0P++Z8ONEIlZjyoTkBuODN+0VfhAyYksKi43l1Sx9tX2VlFfzjfmr5Wl3Q==",
+            "dev": true,
+            "requires": {
+                "@ethersproject/bignumber": "^5.4.0"
+            }
+        },
+        "@ethersproject/contracts": {
+            "version": "5.4.1",
+            "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.4.1.tgz",
+            "integrity": "sha512-m+z2ZgPy4pyR15Je//dUaymRUZq5MtDajF6GwFbGAVmKz/RF+DNIPwF0k5qEcL3wPGVqUjFg2/krlCRVTU4T5w==",
+            "dev": true,
+            "requires": {
+                "@ethersproject/abi": "^5.4.0",
+                "@ethersproject/abstract-provider": "^5.4.0",
+                "@ethersproject/abstract-signer": "^5.4.0",
+                "@ethersproject/address": "^5.4.0",
+                "@ethersproject/bignumber": "^5.4.0",
+                "@ethersproject/bytes": "^5.4.0",
+                "@ethersproject/constants": "^5.4.0",
+                "@ethersproject/logger": "^5.4.0",
+                "@ethersproject/properties": "^5.4.0",
+                "@ethersproject/transactions": "^5.4.0"
+            }
+        },
+        "@ethersproject/hash": {
+            "version": "5.4.0",
+            "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.4.0.tgz",
+            "integrity": "sha512-xymAM9tmikKgbktOCjW60Z5sdouiIIurkZUr9oW5NOex5uwxrbsYG09kb5bMcNjlVeJD3yPivTNzViIs1GCbqA==",
+            "dev": true,
+            "requires": {
+                "@ethersproject/abstract-signer": "^5.4.0",
+                "@ethersproject/address": "^5.4.0",
+                "@ethersproject/bignumber": "^5.4.0",
+                "@ethersproject/bytes": "^5.4.0",
+                "@ethersproject/keccak256": "^5.4.0",
+                "@ethersproject/logger": "^5.4.0",
+                "@ethersproject/properties": "^5.4.0",
+                "@ethersproject/strings": "^5.4.0"
+            }
+        },
+        "@ethersproject/hdnode": {
+            "version": "5.4.0",
+            "resolved": "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.4.0.tgz",
+            "integrity": "sha512-pKxdS0KAaeVGfZPp1KOiDLB0jba11tG6OP1u11QnYfb7pXn6IZx0xceqWRr6ygke8+Kw74IpOoSi7/DwANhy8Q==",
+            "dev": true,
+            "requires": {
+                "@ethersproject/abstract-signer": "^5.4.0",
+                "@ethersproject/basex": "^5.4.0",
+                "@ethersproject/bignumber": "^5.4.0",
+                "@ethersproject/bytes": "^5.4.0",
+                "@ethersproject/logger": "^5.4.0",
+                "@ethersproject/pbkdf2": "^5.4.0",
+                "@ethersproject/properties": "^5.4.0",
+                "@ethersproject/sha2": "^5.4.0",
+                "@ethersproject/signing-key": "^5.4.0",
+                "@ethersproject/strings": "^5.4.0",
+                "@ethersproject/transactions": "^5.4.0",
+                "@ethersproject/wordlists": "^5.4.0"
+            }
+        },
+        "@ethersproject/json-wallets": {
+            "version": "5.4.0",
+            "resolved": "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.4.0.tgz",
+            "integrity": "sha512-igWcu3fx4aiczrzEHwG1xJZo9l1cFfQOWzTqwRw/xcvxTk58q4f9M7cjh51EKphMHvrJtcezJ1gf1q1AUOfEQQ==",
+            "dev": true,
+            "requires": {
+                "@ethersproject/abstract-signer": "^5.4.0",
+                "@ethersproject/address": "^5.4.0",
+                "@ethersproject/bytes": "^5.4.0",
+                "@ethersproject/hdnode": "^5.4.0",
+                "@ethersproject/keccak256": "^5.4.0",
+                "@ethersproject/logger": "^5.4.0",
+                "@ethersproject/pbkdf2": "^5.4.0",
+                "@ethersproject/properties": "^5.4.0",
+                "@ethersproject/random": "^5.4.0",
+                "@ethersproject/strings": "^5.4.0",
+                "@ethersproject/transactions": "^5.4.0",
+                "aes-js": "3.0.0",
+                "scrypt-js": "3.0.1"
+            }
+        },
+        "@ethersproject/keccak256": {
+            "version": "5.4.0",
+            "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.4.0.tgz",
+            "integrity": "sha512-FBI1plWet+dPUvAzPAeHzRKiPpETQzqSUWR1wXJGHVWi4i8bOSrpC3NwpkPjgeXG7MnugVc1B42VbfnQikyC/A==",
+            "dev": true,
+            "requires": {
+                "@ethersproject/bytes": "^5.4.0",
+                "js-sha3": "0.5.7"
+            }
+        },
+        "@ethersproject/logger": {
+            "version": "5.4.1",
+            "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.4.1.tgz",
+            "integrity": "sha512-DZ+bRinnYLPw1yAC64oRl0QyVZj43QeHIhVKfD/+YwSz4wsv1pfwb5SOFjz+r710YEWzU6LrhuSjpSO+6PeE4A==",
+            "dev": true
+        },
+        "@ethersproject/networks": {
+            "version": "5.4.2",
+            "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.4.2.tgz",
+            "integrity": "sha512-eekOhvJyBnuibfJnhtK46b8HimBc5+4gqpvd1/H9LEl7Q7/qhsIhM81dI9Fcnjpk3jB1aTy6bj0hz3cifhNeYw==",
+            "dev": true,
+            "requires": {
+                "@ethersproject/logger": "^5.4.0"
+            }
+        },
+        "@ethersproject/pbkdf2": {
+            "version": "5.4.0",
+            "resolved": "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.4.0.tgz",
+            "integrity": "sha512-x94aIv6tiA04g6BnazZSLoRXqyusawRyZWlUhKip2jvoLpzJuLb//KtMM6PEovE47pMbW+Qe1uw+68ameJjB7g==",
+            "dev": true,
+            "requires": {
+                "@ethersproject/bytes": "^5.4.0",
+                "@ethersproject/sha2": "^5.4.0"
+            }
+        },
+        "@ethersproject/properties": {
+            "version": "5.4.1",
+            "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.4.1.tgz",
+            "integrity": "sha512-cyCGlF8wWlIZyizsj2PpbJ9I7rIlUAfnHYwy/T90pdkSn/NFTa5YWZx2wTJBe9V7dD65dcrrEMisCRUJiq6n3w==",
+            "dev": true,
+            "requires": {
+                "@ethersproject/logger": "^5.4.0"
+            }
+        },
+        "@ethersproject/providers": {
+            "version": "5.4.5",
+            "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.4.5.tgz",
+            "integrity": "sha512-1GkrvkiAw3Fj28cwi1Sqm8ED1RtERtpdXmRfwIBGmqBSN5MoeRUHuwHPppMtbPayPgpFcvD7/Gdc9doO5fGYgw==",
+            "dev": true,
+            "requires": {
+                "@ethersproject/abstract-provider": "^5.4.0",
+                "@ethersproject/abstract-signer": "^5.4.0",
+                "@ethersproject/address": "^5.4.0",
+                "@ethersproject/basex": "^5.4.0",
+                "@ethersproject/bignumber": "^5.4.0",
+                "@ethersproject/bytes": "^5.4.0",
+                "@ethersproject/constants": "^5.4.0",
+                "@ethersproject/hash": "^5.4.0",
+                "@ethersproject/logger": "^5.4.0",
+                "@ethersproject/networks": "^5.4.0",
+                "@ethersproject/properties": "^5.4.0",
+                "@ethersproject/random": "^5.4.0",
+                "@ethersproject/rlp": "^5.4.0",
+                "@ethersproject/sha2": "^5.4.0",
+                "@ethersproject/strings": "^5.4.0",
+                "@ethersproject/transactions": "^5.4.0",
+                "@ethersproject/web": "^5.4.0",
+                "bech32": "1.1.4",
+                "ws": "7.4.6"
+            }
+        },
+        "@ethersproject/random": {
+            "version": "5.4.0",
+            "resolved": "https://registry.npmjs.org/@ethersproject/random/-/random-5.4.0.tgz",
+            "integrity": "sha512-pnpWNQlf0VAZDEOVp1rsYQosmv2o0ITS/PecNw+mS2/btF8eYdspkN0vIXrCMtkX09EAh9bdk8GoXmFXM1eAKw==",
+            "dev": true,
+            "requires": {
+                "@ethersproject/bytes": "^5.4.0",
+                "@ethersproject/logger": "^5.4.0"
+            }
+        },
+        "@ethersproject/rlp": {
+            "version": "5.4.0",
+            "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.4.0.tgz",
+            "integrity": "sha512-0I7MZKfi+T5+G8atId9QaQKHRvvasM/kqLyAH4XxBCBchAooH2EX5rL9kYZWwcm3awYV+XC7VF6nLhfeQFKVPg==",
+            "dev": true,
+            "requires": {
+                "@ethersproject/bytes": "^5.4.0",
+                "@ethersproject/logger": "^5.4.0"
+            }
+        },
+        "@ethersproject/sha2": {
+            "version": "5.4.0",
+            "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.4.0.tgz",
+            "integrity": "sha512-siheo36r1WD7Cy+bDdE1BJ8y0bDtqXCOxRMzPa4bV1TGt/eTUUt03BHoJNB6reWJD8A30E/pdJ8WFkq+/uz4Gg==",
+            "dev": true,
+            "requires": {
+                "@ethersproject/bytes": "^5.4.0",
+                "@ethersproject/logger": "^5.4.0",
+                "hash.js": "1.1.7"
+            }
+        },
+        "@ethersproject/signing-key": {
+            "version": "5.4.0",
+            "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.4.0.tgz",
+            "integrity": "sha512-q8POUeywx6AKg2/jX9qBYZIAmKSB4ubGXdQ88l40hmATj29JnG5pp331nAWwwxPn2Qao4JpWHNZsQN+bPiSW9A==",
+            "dev": true,
+            "requires": {
+                "@ethersproject/bytes": "^5.4.0",
+                "@ethersproject/logger": "^5.4.0",
+                "@ethersproject/properties": "^5.4.0",
+                "bn.js": "^4.11.9",
+                "elliptic": "6.5.4",
+                "hash.js": "1.1.7"
+            }
+        },
+        "@ethersproject/solidity": {
+            "version": "5.4.0",
+            "resolved": "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.4.0.tgz",
+            "integrity": "sha512-XFQTZ7wFSHOhHcV1DpcWj7VXECEiSrBuv7JErJvB9Uo+KfCdc3QtUZV+Vjh/AAaYgezUEKbCtE6Khjm44seevQ==",
+            "dev": true,
+            "requires": {
+                "@ethersproject/bignumber": "^5.4.0",
+                "@ethersproject/bytes": "^5.4.0",
+                "@ethersproject/keccak256": "^5.4.0",
+                "@ethersproject/sha2": "^5.4.0",
+                "@ethersproject/strings": "^5.4.0"
+            }
+        },
+        "@ethersproject/strings": {
+            "version": "5.4.0",
+            "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.4.0.tgz",
+            "integrity": "sha512-k/9DkH5UGDhv7aReXLluFG5ExurwtIpUfnDNhQA29w896Dw3i4uDTz01Quaptbks1Uj9kI8wo9tmW73wcIEaWA==",
+            "dev": true,
+            "requires": {
+                "@ethersproject/bytes": "^5.4.0",
+                "@ethersproject/constants": "^5.4.0",
+                "@ethersproject/logger": "^5.4.0"
+            }
+        },
+        "@ethersproject/transactions": {
+            "version": "5.4.0",
+            "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.4.0.tgz",
+            "integrity": "sha512-s3EjZZt7xa4BkLknJZ98QGoIza94rVjaEed0rzZ/jB9WrIuu/1+tjvYCWzVrystXtDswy7TPBeIepyXwSYa4WQ==",
+            "dev": true,
+            "requires": {
+                "@ethersproject/address": "^5.4.0",
+                "@ethersproject/bignumber": "^5.4.0",
+                "@ethersproject/bytes": "^5.4.0",
+                "@ethersproject/constants": "^5.4.0",
+                "@ethersproject/keccak256": "^5.4.0",
+                "@ethersproject/logger": "^5.4.0",
+                "@ethersproject/properties": "^5.4.0",
+                "@ethersproject/rlp": "^5.4.0",
+                "@ethersproject/signing-key": "^5.4.0"
+            }
+        },
+        "@ethersproject/units": {
+            "version": "5.4.0",
+            "resolved": "https://registry.npmjs.org/@ethersproject/units/-/units-5.4.0.tgz",
+            "integrity": "sha512-Z88krX40KCp+JqPCP5oPv5p750g+uU6gopDYRTBGcDvOASh6qhiEYCRatuM/suC4S2XW9Zz90QI35MfSrTIaFg==",
+            "dev": true,
+            "requires": {
+                "@ethersproject/bignumber": "^5.4.0",
+                "@ethersproject/constants": "^5.4.0",
+                "@ethersproject/logger": "^5.4.0"
+            }
+        },
+        "@ethersproject/wallet": {
+            "version": "5.4.0",
+            "resolved": "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.4.0.tgz",
+            "integrity": "sha512-wU29majLjM6AjCjpat21mPPviG+EpK7wY1+jzKD0fg3ui5fgedf2zEu1RDgpfIMsfn8fJHJuzM4zXZ2+hSHaSQ==",
+            "dev": true,
+            "requires": {
+                "@ethersproject/abstract-provider": "^5.4.0",
+                "@ethersproject/abstract-signer": "^5.4.0",
+                "@ethersproject/address": "^5.4.0",
+                "@ethersproject/bignumber": "^5.4.0",
+                "@ethersproject/bytes": "^5.4.0",
+                "@ethersproject/hash": "^5.4.0",
+                "@ethersproject/hdnode": "^5.4.0",
+                "@ethersproject/json-wallets": "^5.4.0",
+                "@ethersproject/keccak256": "^5.4.0",
+                "@ethersproject/logger": "^5.4.0",
+                "@ethersproject/properties": "^5.4.0",
+                "@ethersproject/random": "^5.4.0",
+                "@ethersproject/signing-key": "^5.4.0",
+                "@ethersproject/transactions": "^5.4.0",
+                "@ethersproject/wordlists": "^5.4.0"
+            }
+        },
+        "@ethersproject/web": {
+            "version": "5.4.0",
+            "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.4.0.tgz",
+            "integrity": "sha512-1bUusGmcoRLYgMn6c1BLk1tOKUIFuTg8j+6N8lYlbMpDesnle+i3pGSagGNvwjaiLo4Y5gBibwctpPRmjrh4Og==",
+            "dev": true,
+            "requires": {
+                "@ethersproject/base64": "^5.4.0",
+                "@ethersproject/bytes": "^5.4.0",
+                "@ethersproject/logger": "^5.4.0",
+                "@ethersproject/properties": "^5.4.0",
+                "@ethersproject/strings": "^5.4.0"
+            }
+        },
+        "@ethersproject/wordlists": {
+            "version": "5.4.0",
+            "resolved": "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.4.0.tgz",
+            "integrity": "sha512-FemEkf6a+EBKEPxlzeVgUaVSodU7G0Na89jqKjmWMlDB0tomoU8RlEMgUvXyqtrg8N4cwpLh8nyRnm1Nay1isA==",
+            "dev": true,
+            "requires": {
+                "@ethersproject/bytes": "^5.4.0",
+                "@ethersproject/hash": "^5.4.0",
+                "@ethersproject/logger": "^5.4.0",
+                "@ethersproject/properties": "^5.4.0",
+                "@ethersproject/strings": "^5.4.0"
+            }
+        },
+        "@improbable-eng/grpc-web": {
+            "version": "0.14.1",
+            "resolved": "https://registry.npmjs.org/@improbable-eng/grpc-web/-/grpc-web-0.14.1.tgz",
+            "integrity": "sha512-XaIYuunepPxoiGVLLHmlnVminUGzBTnXr8Wv7khzmLWbNw4TCwJKX09GSMJlKhu/TRk6gms0ySFxewaETSBqgw==",
+            "peer": true,
+            "requires": {
+                "browser-headers": "^0.4.1"
+            }
+        },
+        "@improbable-eng/grpc-web-node-http-transport": {
+            "version": "0.14.1",
+            "resolved": "https://registry.npmjs.org/@improbable-eng/grpc-web-node-http-transport/-/grpc-web-node-http-transport-0.14.1.tgz",
+            "integrity": "sha512-ZsCTzI1iKUbmQjB5DNZSI5/hvdliuaPpS2h8mVj1QzynL3IFb5NrNnHVHbfcH1wbm26Ka6Z1CrKFGvKLrmbFIg==",
+            "requires": {}
+        },
+        "@openzeppelin/contracts": {
+            "version": "4.3.1",
+            "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-4.3.1.tgz",
+            "integrity": "sha512-QjgbPPlmDK2clK1hzjw2ROfY8KA5q+PfhDUUxZFEBCZP9fi6d5FuNoh/Uq0oCTMEKPmue69vhX2jcl0N/tFKGw==",
+            "dev": true
+        },
+        "@protobufjs/aspromise": {
+            "version": "1.1.2",
+            "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
+            "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78="
+        },
+        "@protobufjs/base64": {
+            "version": "1.1.2",
+            "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
+            "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="
+        },
+        "@protobufjs/codegen": {
+            "version": "2.0.4",
+            "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
+            "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="
+        },
+        "@protobufjs/eventemitter": {
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
+            "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A="
+        },
+        "@protobufjs/fetch": {
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
+            "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=",
+            "requires": {
+                "@protobufjs/aspromise": "^1.1.1",
+                "@protobufjs/inquire": "^1.1.0"
+            }
+        },
+        "@protobufjs/float": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
+            "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E="
+        },
+        "@protobufjs/inquire": {
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
+            "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik="
+        },
+        "@protobufjs/path": {
+            "version": "1.1.2",
+            "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
+            "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0="
+        },
+        "@protobufjs/pool": {
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
+            "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q="
+        },
+        "@protobufjs/utf8": {
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
+            "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA="
+        },
+        "@typechain/ethers-v5": {
+            "version": "7.1.2",
+            "resolved": "https://registry.npmjs.org/@typechain/ethers-v5/-/ethers-v5-7.1.2.tgz",
+            "integrity": "sha512-sD4HVkTL5aIJa3Ft+CmqiOapba0zzZ8xa+QywcWH40Rm/dcxvZWwcCMnnI3En0JebkxOcAVfH3do+kQ9rKSxYw==",
+            "dev": true,
+            "requires": {
+                "lodash": "^4.17.15",
+                "ts-essentials": "^7.0.1"
+            }
+        },
+        "@types/eslint": {
+            "version": "7.28.0",
+            "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.28.0.tgz",
+            "integrity": "sha512-07XlgzX0YJUn4iG1ocY4IX9DzKSmMGUs6ESKlxWhZRaa0fatIWaHWUVapcuGa8r5HFnTqzj+4OCjd5f7EZ/i/A==",
+            "dev": true,
+            "peer": true,
+            "requires": {
+                "@types/estree": "*",
+                "@types/json-schema": "*"
+            }
+        },
+        "@types/eslint-scope": {
+            "version": "3.7.1",
+            "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.1.tgz",
+            "integrity": "sha512-SCFeogqiptms4Fg29WpOTk5nHIzfpKCemSN63ksBQYKTcXoJEmJagV+DhVmbapZzY4/5YaOV1nZwrsU79fFm1g==",
+            "dev": true,
+            "peer": true,
+            "requires": {
+                "@types/eslint": "*",
+                "@types/estree": "*"
+            }
+        },
+        "@types/estree": {
+            "version": "0.0.50",
+            "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz",
+            "integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==",
+            "dev": true,
+            "peer": true
+        },
+        "@types/json-schema": {
+            "version": "7.0.9",
+            "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz",
+            "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==",
+            "dev": true,
+            "peer": true
+        },
+        "@types/long": {
+            "version": "4.0.1",
+            "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz",
+            "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w=="
+        },
+        "@types/node": {
+            "version": "16.9.1",
+            "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.1.tgz",
+            "integrity": "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g=="
+        },
+        "@types/object-hash": {
+            "version": "1.3.4",
+            "resolved": "https://registry.npmjs.org/@types/object-hash/-/object-hash-1.3.4.tgz",
+            "integrity": "sha512-xFdpkAkikBgqBdG9vIlsqffDV8GpvnPEzs0IUtr1v3BEB97ijsFQ4RXVbUZwjFThhB4MDSTUfvmxUD5PGx0wXA=="
+        },
+        "@types/prettier": {
+            "version": "2.3.2",
+            "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.3.2.tgz",
+            "integrity": "sha512-eI5Yrz3Qv4KPUa/nSIAi0h+qX0XyewOliug5F2QAtuRg6Kjg6jfmxe1GIwoIRhZspD1A0RP8ANrPwvEXXtRFog==",
+            "dev": true,
+            "peer": true
+        },
+        "@webassemblyjs/ast": {
+            "version": "1.11.1",
+            "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz",
+            "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==",
+            "dev": true,
+            "peer": true,
+            "requires": {
+                "@webassemblyjs/helper-numbers": "1.11.1",
+                "@webassemblyjs/helper-wasm-bytecode": "1.11.1"
+            }
+        },
+        "@webassemblyjs/floating-point-hex-parser": {
+            "version": "1.11.1",
+            "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz",
+            "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==",
+            "dev": true,
+            "peer": true
+        },
+        "@webassemblyjs/helper-api-error": {
+            "version": "1.11.1",
+            "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz",
+            "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==",
+            "dev": true,
+            "peer": true
+        },
+        "@webassemblyjs/helper-buffer": {
+            "version": "1.11.1",
+            "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz",
+            "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==",
+            "dev": true,
+            "peer": true
+        },
+        "@webassemblyjs/helper-numbers": {
+            "version": "1.11.1",
+            "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz",
+            "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==",
+            "dev": true,
+            "peer": true,
+            "requires": {
+                "@webassemblyjs/floating-point-hex-parser": "1.11.1",
+                "@webassemblyjs/helper-api-error": "1.11.1",
+                "@xtuc/long": "4.2.2"
+            }
+        },
+        "@webassemblyjs/helper-wasm-bytecode": {
+            "version": "1.11.1",
+            "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz",
+            "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==",
+            "dev": true,
+            "peer": true
+        },
+        "@webassemblyjs/helper-wasm-section": {
+            "version": "1.11.1",
+            "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz",
+            "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==",
+            "dev": true,
+            "peer": true,
+            "requires": {
+                "@webassemblyjs/ast": "1.11.1",
+                "@webassemblyjs/helper-buffer": "1.11.1",
+                "@webassemblyjs/helper-wasm-bytecode": "1.11.1",
+                "@webassemblyjs/wasm-gen": "1.11.1"
+            }
+        },
+        "@webassemblyjs/ieee754": {
+            "version": "1.11.1",
+            "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz",
+            "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==",
+            "dev": true,
+            "peer": true,
+            "requires": {
+                "@xtuc/ieee754": "^1.2.0"
+            }
+        },
+        "@webassemblyjs/leb128": {
+            "version": "1.11.1",
+            "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz",
+            "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==",
+            "dev": true,
+            "peer": true,
+            "requires": {
+                "@xtuc/long": "4.2.2"
+            }
+        },
+        "@webassemblyjs/utf8": {
+            "version": "1.11.1",
+            "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz",
+            "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==",
+            "dev": true,
+            "peer": true
+        },
+        "@webassemblyjs/wasm-edit": {
+            "version": "1.11.1",
+            "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz",
+            "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==",
+            "dev": true,
+            "peer": true,
+            "requires": {
+                "@webassemblyjs/ast": "1.11.1",
+                "@webassemblyjs/helper-buffer": "1.11.1",
+                "@webassemblyjs/helper-wasm-bytecode": "1.11.1",
+                "@webassemblyjs/helper-wasm-section": "1.11.1",
+                "@webassemblyjs/wasm-gen": "1.11.1",
+                "@webassemblyjs/wasm-opt": "1.11.1",
+                "@webassemblyjs/wasm-parser": "1.11.1",
+                "@webassemblyjs/wast-printer": "1.11.1"
+            }
+        },
+        "@webassemblyjs/wasm-gen": {
+            "version": "1.11.1",
+            "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz",
+            "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==",
+            "dev": true,
+            "peer": true,
+            "requires": {
+                "@webassemblyjs/ast": "1.11.1",
+                "@webassemblyjs/helper-wasm-bytecode": "1.11.1",
+                "@webassemblyjs/ieee754": "1.11.1",
+                "@webassemblyjs/leb128": "1.11.1",
+                "@webassemblyjs/utf8": "1.11.1"
+            }
+        },
+        "@webassemblyjs/wasm-opt": {
+            "version": "1.11.1",
+            "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz",
+            "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==",
+            "dev": true,
+            "peer": true,
+            "requires": {
+                "@webassemblyjs/ast": "1.11.1",
+                "@webassemblyjs/helper-buffer": "1.11.1",
+                "@webassemblyjs/wasm-gen": "1.11.1",
+                "@webassemblyjs/wasm-parser": "1.11.1"
+            }
+        },
+        "@webassemblyjs/wasm-parser": {
+            "version": "1.11.1",
+            "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz",
+            "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==",
+            "dev": true,
+            "peer": true,
+            "requires": {
+                "@webassemblyjs/ast": "1.11.1",
+                "@webassemblyjs/helper-api-error": "1.11.1",
+                "@webassemblyjs/helper-wasm-bytecode": "1.11.1",
+                "@webassemblyjs/ieee754": "1.11.1",
+                "@webassemblyjs/leb128": "1.11.1",
+                "@webassemblyjs/utf8": "1.11.1"
+            }
+        },
+        "@webassemblyjs/wast-printer": {
+            "version": "1.11.1",
+            "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz",
+            "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==",
+            "dev": true,
+            "peer": true,
+            "requires": {
+                "@webassemblyjs/ast": "1.11.1",
+                "@xtuc/long": "4.2.2"
+            }
+        },
+        "@xtuc/ieee754": {
+            "version": "1.2.0",
+            "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
+            "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==",
+            "dev": true,
+            "peer": true
+        },
+        "@xtuc/long": {
+            "version": "4.2.2",
+            "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
+            "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
+            "dev": true,
+            "peer": true
+        },
+        "acorn": {
+            "version": "8.5.0",
+            "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz",
+            "integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==",
+            "dev": true,
+            "peer": true
+        },
+        "acorn-import-assertions": {
+            "version": "1.7.6",
+            "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.7.6.tgz",
+            "integrity": "sha512-FlVvVFA1TX6l3lp8VjDnYYq7R1nyW6x3svAt4nDgrWQ9SBaSh9CnbwgSUTasgfNfOG5HlM1ehugCvM+hjo56LA==",
+            "dev": true,
+            "peer": true,
+            "requires": {}
+        },
+        "aes-js": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz",
+            "integrity": "sha1-4h3xCtbCBTKVvLuNq0Cwnb6ofk0=",
+            "dev": true
+        },
+        "ajv": {
+            "version": "6.12.6",
+            "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+            "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+            "dev": true,
+            "peer": true,
+            "requires": {
+                "fast-deep-equal": "^3.1.1",
+                "fast-json-stable-stringify": "^2.0.0",
+                "json-schema-traverse": "^0.4.1",
+                "uri-js": "^4.2.2"
+            }
+        },
+        "ajv-keywords": {
+            "version": "3.5.2",
+            "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
+            "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
+            "dev": true,
+            "peer": true,
+            "requires": {}
+        },
+        "ansi-styles": {
+            "version": "3.2.1",
+            "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+            "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+            "dev": true,
+            "requires": {
+                "color-convert": "^1.9.0"
+            }
+        },
+        "argparse": {
+            "version": "1.0.10",
+            "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+            "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+            "dev": true,
+            "requires": {
+                "sprintf-js": "~1.0.2"
+            }
+        },
+        "array-back": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/array-back/-/array-back-2.0.0.tgz",
+            "integrity": "sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==",
+            "dev": true,
+            "peer": true,
+            "requires": {
+                "typical": "^2.6.1"
+            }
+        },
+        "balanced-match": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+            "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+            "dev": true
+        },
+        "bech32": {
+            "version": "1.1.4",
+            "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz",
+            "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==",
+            "dev": true
+        },
+        "bn.js": {
+            "version": "4.12.0",
+            "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
+            "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
+            "dev": true
+        },
+        "brace-expansion": {
+            "version": "1.1.11",
+            "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+            "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+            "dev": true,
+            "requires": {
+                "balanced-match": "^1.0.0",
+                "concat-map": "0.0.1"
+            }
+        },
+        "braces": {
+            "version": "3.0.2",
+            "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+            "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+            "dev": true,
+            "requires": {
+                "fill-range": "^7.0.1"
+            }
+        },
+        "brorand": {
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
+            "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=",
+            "dev": true
+        },
+        "browser-headers": {
+            "version": "0.4.1",
+            "resolved": "https://registry.npmjs.org/browser-headers/-/browser-headers-0.4.1.tgz",
+            "integrity": "sha512-CA9hsySZVo9371qEHjHZtYxV2cFtVj5Wj/ZHi8ooEsrtm4vOnl9Y9HmyYWk9q+05d7K3rdoAE0j3MVEFVvtQtg==",
+            "peer": true
+        },
+        "browserslist": {
+            "version": "4.17.0",
+            "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.17.0.tgz",
+            "integrity": "sha512-g2BJ2a0nEYvEFQC208q8mVAhfNwpZ5Mu8BwgtCdZKO3qx98HChmeg448fPdUzld8aFmfLgVh7yymqV+q1lJZ5g==",
+            "dev": true,
+            "peer": true,
+            "requires": {
+                "caniuse-lite": "^1.0.30001254",
+                "colorette": "^1.3.0",
+                "electron-to-chromium": "^1.3.830",
+                "escalade": "^3.1.1",
+                "node-releases": "^1.1.75"
+            }
+        },
+        "buffer-from": {
+            "version": "1.1.2",
+            "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
+            "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
+            "dev": true,
+            "peer": true
+        },
+        "bufferutil": {
+            "version": "4.0.3",
+            "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.3.tgz",
+            "integrity": "sha512-yEYTwGndELGvfXsImMBLop58eaGW+YdONi1fNjTINSY98tmMmFijBG6WXgdkfuLNt4imzQNtIE+eBp1PVpMCSw==",
+            "dev": true,
+            "optional": true,
+            "peer": true,
+            "requires": {
+                "node-gyp-build": "^4.2.0"
+            }
+        },
+        "builtin-modules": {
+            "version": "1.1.1",
+            "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz",
+            "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=",
+            "dev": true
+        },
+        "caniuse-lite": {
+            "version": "1.0.30001257",
+            "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001257.tgz",
+            "integrity": "sha512-JN49KplOgHSXpIsVSF+LUyhD8PUp6xPpAXeRrrcBh4KBeP7W864jHn6RvzJgDlrReyeVjMFJL3PLpPvKIxlIHA==",
+            "dev": true,
+            "peer": true
+        },
+        "chalk": {
+            "version": "2.4.2",
+            "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+            "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+            "dev": true,
+            "requires": {
+                "ansi-styles": "^3.2.1",
+                "escape-string-regexp": "^1.0.5",
+                "supports-color": "^5.3.0"
+            }
+        },
+        "chrome-trace-event": {
+            "version": "1.0.3",
+            "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz",
+            "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==",
+            "dev": true,
+            "peer": true
+        },
+        "color-convert": {
+            "version": "1.9.3",
+            "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+            "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+            "dev": true,
+            "requires": {
+                "color-name": "1.1.3"
+            }
+        },
+        "color-name": {
+            "version": "1.1.3",
+            "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+            "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+            "dev": true
+        },
+        "colorette": {
+            "version": "1.4.0",
+            "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz",
+            "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==",
+            "dev": true,
+            "peer": true
+        },
+        "command-line-args": {
+            "version": "4.0.7",
+            "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-4.0.7.tgz",
+            "integrity": "sha512-aUdPvQRAyBvQd2n7jXcsMDz68ckBJELXNzBybCHOibUWEg0mWTnaYCSRU8h9R+aNRSvDihJtssSRCiDRpLaezA==",
+            "dev": true,
+            "peer": true,
+            "requires": {
+                "array-back": "^2.0.0",
+                "find-replace": "^1.0.3",
+                "typical": "^2.6.1"
+            }
+        },
+        "commander": {
+            "version": "2.20.3",
+            "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+            "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+            "dev": true
+        },
+        "concat-map": {
+            "version": "0.0.1",
+            "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+            "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+            "dev": true
+        },
+        "copy-dir": {
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/copy-dir/-/copy-dir-1.3.0.tgz",
+            "integrity": "sha512-Q4+qBFnN4bwGwvtXXzbp4P/4iNk0MaiGAzvQ8OiMtlLjkIKjmNN689uVzShSM0908q7GoFHXIPx4zi75ocoaHw==",
+            "dev": true
+        },
+        "dataloader": {
+            "version": "1.4.0",
+            "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-1.4.0.tgz",
+            "integrity": "sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw=="
+        },
+        "debug": {
+            "version": "4.3.2",
+            "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz",
+            "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==",
+            "dev": true,
+            "peer": true,
+            "requires": {
+                "ms": "2.1.2"
+            }
+        },
+        "diff": {
+            "version": "4.0.2",
+            "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
+            "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
+            "dev": true
+        },
+        "electron-to-chromium": {
+            "version": "1.3.836",
+            "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.836.tgz",
+            "integrity": "sha512-Ney3pHOJBWkG/AqYjrW0hr2AUCsao+2uvq9HUlRP8OlpSdk/zOHOUJP7eu0icDvePC9DlgffuelP4TnOJmMRUg==",
+            "dev": true,
+            "peer": true
+        },
+        "elliptic": {
+            "version": "6.5.4",
+            "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz",
+            "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==",
+            "dev": true,
+            "requires": {
+                "bn.js": "^4.11.9",
+                "brorand": "^1.1.0",
+                "hash.js": "^1.0.0",
+                "hmac-drbg": "^1.0.1",
+                "inherits": "^2.0.4",
+                "minimalistic-assert": "^1.0.1",
+                "minimalistic-crypto-utils": "^1.0.1"
+            }
+        },
+        "enhanced-resolve": {
+            "version": "5.8.2",
+            "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.8.2.tgz",
+            "integrity": "sha512-F27oB3WuHDzvR2DOGNTaYy0D5o0cnrv8TeI482VM4kYgQd/FT9lUQwuNsJ0oOHtBUq7eiW5ytqzp7nBFknL+GA==",
+            "dev": true,
+            "requires": {
+                "graceful-fs": "^4.2.4",
+                "tapable": "^2.2.0"
+            }
+        },
+        "es-module-lexer": {
+            "version": "0.7.1",
+            "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.7.1.tgz",
+            "integrity": "sha512-MgtWFl5No+4S3TmhDmCz2ObFGm6lEpTnzbQi+Dd+pw4mlTIZTmM2iAs5gRlmx5zS9luzobCSBSI90JM/1/JgOw==",
+            "dev": true,
+            "peer": true
+        },
+        "escalade": {
+            "version": "3.1.1",
+            "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
+            "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
+            "dev": true,
+            "peer": true
+        },
+        "escape-string-regexp": {
+            "version": "1.0.5",
+            "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+            "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+            "dev": true
+        },
+        "eslint-scope": {
+            "version": "5.1.1",
+            "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
+            "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
+            "dev": true,
+            "peer": true,
+            "requires": {
+                "esrecurse": "^4.3.0",
+                "estraverse": "^4.1.1"
+            }
+        },
+        "esm": {
+            "version": "3.2.25",
+            "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz",
+            "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==",
+            "dev": true
+        },
+        "esprima": {
+            "version": "4.0.1",
+            "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+            "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+            "dev": true
+        },
+        "esrecurse": {
+            "version": "4.3.0",
+            "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+            "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+            "dev": true,
+            "peer": true,
+            "requires": {
+                "estraverse": "^5.2.0"
+            },
+            "dependencies": {
+                "estraverse": {
+                    "version": "5.2.0",
+                    "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz",
+                    "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==",
+                    "dev": true,
+                    "peer": true
+                }
+            }
+        },
+        "estraverse": {
+            "version": "4.3.0",
+            "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
+            "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
+            "dev": true,
+            "peer": true
+        },
+        "ethers": {
+            "version": "5.4.7",
+            "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.4.7.tgz",
+            "integrity": "sha512-iZc5p2nqfWK1sj8RabwsPM28cr37Bpq7ehTQ5rWExBr2Y09Sn1lDKZOED26n+TsZMye7Y6mIgQ/1cwpSD8XZew==",
+            "dev": true,
+            "requires": {
+                "@ethersproject/abi": "5.4.1",
+                "@ethersproject/abstract-provider": "5.4.1",
+                "@ethersproject/abstract-signer": "5.4.1",
+                "@ethersproject/address": "5.4.0",
+                "@ethersproject/base64": "5.4.0",
+                "@ethersproject/basex": "5.4.0",
+                "@ethersproject/bignumber": "5.4.2",
+                "@ethersproject/bytes": "5.4.0",
+                "@ethersproject/constants": "5.4.0",
+                "@ethersproject/contracts": "5.4.1",
+                "@ethersproject/hash": "5.4.0",
+                "@ethersproject/hdnode": "5.4.0",
+                "@ethersproject/json-wallets": "5.4.0",
+                "@ethersproject/keccak256": "5.4.0",
+                "@ethersproject/logger": "5.4.1",
+                "@ethersproject/networks": "5.4.2",
+                "@ethersproject/pbkdf2": "5.4.0",
+                "@ethersproject/properties": "5.4.1",
+                "@ethersproject/providers": "5.4.5",
+                "@ethersproject/random": "5.4.0",
+                "@ethersproject/rlp": "5.4.0",
+                "@ethersproject/sha2": "5.4.0",
+                "@ethersproject/signing-key": "5.4.0",
+                "@ethersproject/solidity": "5.4.0",
+                "@ethersproject/strings": "5.4.0",
+                "@ethersproject/transactions": "5.4.0",
+                "@ethersproject/units": "5.4.0",
+                "@ethersproject/wallet": "5.4.0",
+                "@ethersproject/web": "5.4.0",
+                "@ethersproject/wordlists": "5.4.0"
+            }
+        },
+        "events": {
+            "version": "3.3.0",
+            "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
+            "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
+            "dev": true,
+            "peer": true
+        },
+        "fast-deep-equal": {
+            "version": "3.1.3",
+            "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+            "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+            "dev": true,
+            "peer": true
+        },
+        "fast-json-stable-stringify": {
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+            "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+            "dev": true,
+            "peer": true
+        },
+        "fill-range": {
+            "version": "7.0.1",
+            "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+            "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+            "dev": true,
+            "requires": {
+                "to-regex-range": "^5.0.1"
+            }
+        },
+        "find": {
+            "version": "0.3.0",
+            "resolved": "https://registry.npmjs.org/find/-/find-0.3.0.tgz",
+            "integrity": "sha512-iSd+O4OEYV/I36Zl8MdYJO0xD82wH528SaCieTVHhclgiYNe9y+yPKSwK+A7/WsmHL1EZ+pYUJBXWTL5qofksw==",
+            "dev": true,
+            "requires": {
+                "traverse-chain": "~0.1.0"
+            }
+        },
+        "find-replace": {
+            "version": "1.0.3",
+            "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-1.0.3.tgz",
+            "integrity": "sha1-uI5zZNLZyVlVnziMZmcNYTBEH6A=",
+            "dev": true,
+            "peer": true,
+            "requires": {
+                "array-back": "^1.0.4",
+                "test-value": "^2.1.0"
+            },
+            "dependencies": {
+                "array-back": {
+                    "version": "1.0.4",
+                    "resolved": "https://registry.npmjs.org/array-back/-/array-back-1.0.4.tgz",
+                    "integrity": "sha1-ZEun8JX3/898Q7Xw3DnTwfA8Bjs=",
+                    "dev": true,
+                    "peer": true,
+                    "requires": {
+                        "typical": "^2.6.0"
+                    }
+                }
+            }
+        },
+        "fs-extra": {
+            "version": "7.0.1",
+            "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
+            "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
+            "dev": true,
+            "peer": true,
+            "requires": {
+                "graceful-fs": "^4.1.2",
+                "jsonfile": "^4.0.0",
+                "universalify": "^0.1.0"
+            }
+        },
+        "fs.realpath": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+            "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+            "dev": true
+        },
+        "function-bind": {
+            "version": "1.1.1",
+            "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+            "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+            "dev": true
+        },
+        "glob": {
+            "version": "7.1.7",
+            "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz",
+            "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==",
+            "dev": true,
+            "requires": {
+                "fs.realpath": "^1.0.0",
+                "inflight": "^1.0.4",
+                "inherits": "2",
+                "minimatch": "^3.0.4",
+                "once": "^1.3.0",
+                "path-is-absolute": "^1.0.0"
+            }
+        },
+        "glob-to-regexp": {
+            "version": "0.4.1",
+            "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
+            "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
+            "dev": true,
+            "peer": true
+        },
+        "google-protobuf": {
+            "version": "3.18.0",
+            "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.18.0.tgz",
+            "integrity": "sha512-WlaQWRkUOo/lm9uTgNH6nk9IQt814RggWPzKBfnAVewOFzSzRUSmS1yUWRT6ixW1vS7er5p6tmLSmwzpPpmc8A==",
+            "peer": true
+        },
+        "graceful-fs": {
+            "version": "4.2.8",
+            "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz",
+            "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==",
+            "dev": true
+        },
+        "has": {
+            "version": "1.0.3",
+            "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+            "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+            "dev": true,
+            "requires": {
+                "function-bind": "^1.1.1"
+            }
+        },
+        "has-flag": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+            "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+            "dev": true
+        },
+        "hash.js": {
+            "version": "1.1.7",
+            "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
+            "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
+            "dev": true,
+            "requires": {
+                "inherits": "^2.0.3",
+                "minimalistic-assert": "^1.0.1"
+            }
+        },
+        "hmac-drbg": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
+            "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
+            "dev": true,
+            "requires": {
+                "hash.js": "^1.0.3",
+                "minimalistic-assert": "^1.0.0",
+                "minimalistic-crypto-utils": "^1.0.1"
+            }
+        },
+        "inflight": {
+            "version": "1.0.6",
+            "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+            "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+            "dev": true,
+            "requires": {
+                "once": "^1.3.0",
+                "wrappy": "1"
+            }
+        },
+        "inherits": {
+            "version": "2.0.4",
+            "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+            "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+            "dev": true
+        },
+        "is-core-module": {
+            "version": "2.6.0",
+            "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.6.0.tgz",
+            "integrity": "sha512-wShG8vs60jKfPWpF2KZRaAtvt3a20OAn7+IJ6hLPECpSABLcKtFKTTI4ZtH5QcBruBHlq+WsdHWyz0BCZW7svQ==",
+            "dev": true,
+            "requires": {
+                "has": "^1.0.3"
+            }
+        },
+        "is-number": {
+            "version": "7.0.0",
+            "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+            "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+            "dev": true
+        },
+        "jest-worker": {
+            "version": "27.2.0",
+            "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.2.0.tgz",
+            "integrity": "sha512-laB0ZVIBz+voh/QQy9dmUuuDsadixeerrKqyVpgPz+CCWiOYjOBabUXHIXZhsdvkWbLqSHbgkAHWl5cg24Q6RA==",
+            "dev": true,
+            "peer": true,
+            "requires": {
+                "@types/node": "*",
+                "merge-stream": "^2.0.0",
+                "supports-color": "^8.0.0"
+            },
+            "dependencies": {
+                "has-flag": {
+                    "version": "4.0.0",
+                    "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+                    "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+                    "dev": true,
+                    "peer": true
+                },
+                "supports-color": {
+                    "version": "8.1.1",
+                    "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+                    "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+                    "dev": true,
+                    "peer": true,
+                    "requires": {
+                        "has-flag": "^4.0.0"
+                    }
+                }
+            }
+        },
+        "js-sha3": {
+            "version": "0.5.7",
+            "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.7.tgz",
+            "integrity": "sha1-DU/9gALVMzqrr0oj7tL2N0yfKOc=",
+            "dev": true
+        },
+        "js-tokens": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+            "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+            "dev": true
+        },
+        "js-yaml": {
+            "version": "3.14.1",
+            "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
+            "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
+            "dev": true,
+            "requires": {
+                "argparse": "^1.0.7",
+                "esprima": "^4.0.0"
+            }
+        },
+        "json-parse-better-errors": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
+            "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
+            "dev": true,
+            "peer": true
+        },
+        "json-schema-traverse": {
+            "version": "0.4.1",
+            "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+            "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+            "dev": true,
+            "peer": true
+        },
+        "jsonfile": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
+            "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
+            "dev": true,
+            "peer": true,
+            "requires": {
+                "graceful-fs": "^4.1.6"
+            }
+        },
+        "loader-runner": {
+            "version": "4.2.0",
+            "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz",
+            "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==",
+            "dev": true,
+            "peer": true
+        },
+        "lodash": {
+            "version": "4.17.21",
+            "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+            "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+        },
+        "long": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
+            "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="
+        },
+        "lru-cache": {
+            "version": "6.0.0",
+            "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+            "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+            "dev": true,
+            "requires": {
+                "yallist": "^4.0.0"
+            }
+        },
+        "merge-stream": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+            "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+            "dev": true,
+            "peer": true
+        },
+        "micromatch": {
+            "version": "4.0.4",
+            "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
+            "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==",
+            "dev": true,
+            "requires": {
+                "braces": "^3.0.1",
+                "picomatch": "^2.2.3"
+            }
+        },
+        "mime-db": {
+            "version": "1.49.0",
+            "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz",
+            "integrity": "sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==",
+            "dev": true,
+            "peer": true
+        },
+        "mime-types": {
+            "version": "2.1.32",
+            "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.32.tgz",
+            "integrity": "sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==",
+            "dev": true,
+            "peer": true,
+            "requires": {
+                "mime-db": "1.49.0"
+            }
+        },
+        "minimalistic-assert": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
+            "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
+            "dev": true
+        },
+        "minimalistic-crypto-utils": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
+            "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=",
+            "dev": true
+        },
+        "minimatch": {
+            "version": "3.0.4",
+            "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+            "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+            "dev": true,
+            "requires": {
+                "brace-expansion": "^1.1.7"
+            }
+        },
+        "minimist": {
+            "version": "1.2.5",
+            "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
+            "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
+            "dev": true
+        },
+        "mkdirp": {
+            "version": "0.5.5",
+            "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
+            "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
+            "dev": true,
+            "requires": {
+                "minimist": "^1.2.5"
+            }
+        },
+        "ms": {
+            "version": "2.1.2",
+            "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+            "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+            "dev": true,
+            "peer": true
+        },
+        "neo-async": {
+            "version": "2.6.2",
+            "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
+            "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
+            "dev": true,
+            "peer": true
+        },
+        "node-gyp-build": {
+            "version": "4.2.3",
+            "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.3.tgz",
+            "integrity": "sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg==",
+            "dev": true,
+            "optional": true,
+            "peer": true
+        },
+        "node-releases": {
+            "version": "1.1.75",
+            "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.75.tgz",
+            "integrity": "sha512-Qe5OUajvqrqDSy6wrWFmMwfJ0jVgwiw4T3KqmbTcZ62qW0gQkheXYhcFM1+lOVcGUoRxcEcfyvFMAnDgaF1VWw==",
+            "dev": true,
+            "peer": true
+        },
+        "object-hash": {
+            "version": "1.3.1",
+            "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-1.3.1.tgz",
+            "integrity": "sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA=="
+        },
+        "once": {
+            "version": "1.4.0",
+            "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+            "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+            "dev": true,
+            "requires": {
+                "wrappy": "1"
+            }
+        },
+        "p-limit": {
+            "version": "3.1.0",
+            "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+            "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+            "dev": true,
+            "peer": true,
+            "requires": {
+                "yocto-queue": "^0.1.0"
+            }
+        },
+        "path-is-absolute": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+            "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+            "dev": true
+        },
+        "path-parse": {
+            "version": "1.0.7",
+            "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+            "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+            "dev": true
+        },
+        "picomatch": {
+            "version": "2.3.0",
+            "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz",
+            "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==",
+            "dev": true
+        },
+        "prettier": {
+            "version": "2.4.0",
+            "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.4.0.tgz",
+            "integrity": "sha512-DsEPLY1dE5HF3BxCRBmD4uYZ+5DCbvatnolqTqcxEgKVZnL2kUfyu7b8pPQ5+hTBkdhU9SLUmK0/pHb07RE4WQ=="
+        },
+        "protobufjs": {
+            "version": "6.11.2",
+            "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.2.tgz",
+            "integrity": "sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw==",
+            "requires": {
+                "@protobufjs/aspromise": "^1.1.2",
+                "@protobufjs/base64": "^1.1.2",
+                "@protobufjs/codegen": "^2.0.4",
+                "@protobufjs/eventemitter": "^1.1.0",
+                "@protobufjs/fetch": "^1.1.0",
+                "@protobufjs/float": "^1.0.2",
+                "@protobufjs/inquire": "^1.1.0",
+                "@protobufjs/path": "^1.1.2",
+                "@protobufjs/pool": "^1.1.0",
+                "@protobufjs/utf8": "^1.1.0",
+                "@types/long": "^4.0.1",
+                "@types/node": ">=13.7.0",
+                "long": "^4.0.0"
+            }
+        },
+        "punycode": {
+            "version": "2.1.1",
+            "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+            "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
+            "dev": true,
+            "peer": true
+        },
+        "randombytes": {
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
+            "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+            "dev": true,
+            "peer": true,
+            "requires": {
+                "safe-buffer": "^5.1.0"
+            }
+        },
+        "resolve": {
+            "version": "1.20.0",
+            "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz",
+            "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==",
+            "dev": true,
+            "requires": {
+                "is-core-module": "^2.2.0",
+                "path-parse": "^1.0.6"
+            }
+        },
+        "safe-buffer": {
+            "version": "5.2.1",
+            "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+            "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+            "dev": true,
+            "peer": true
+        },
+        "schema-utils": {
+            "version": "3.1.1",
+            "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
+            "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==",
+            "dev": true,
+            "peer": true,
+            "requires": {
+                "@types/json-schema": "^7.0.8",
+                "ajv": "^6.12.5",
+                "ajv-keywords": "^3.5.2"
+            }
+        },
+        "scrypt-js": {
+            "version": "3.0.1",
+            "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz",
+            "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==",
+            "dev": true
+        },
+        "semver": {
+            "version": "5.7.1",
+            "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+            "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+            "dev": true
+        },
+        "serialize-javascript": {
+            "version": "6.0.0",
+            "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz",
+            "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==",
+            "dev": true,
+            "peer": true,
+            "requires": {
+                "randombytes": "^2.1.0"
+            }
+        },
+        "source-map-support": {
+            "version": "0.5.20",
+            "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.20.tgz",
+            "integrity": "sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==",
+            "dev": true,
+            "peer": true,
+            "requires": {
+                "buffer-from": "^1.0.0",
+                "source-map": "^0.6.0"
+            },
+            "dependencies": {
+                "source-map": {
+                    "version": "0.6.1",
+                    "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+                    "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+                    "dev": true,
+                    "peer": true
+                }
+            }
+        },
+        "sprintf-js": {
+            "version": "1.0.3",
+            "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+            "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
+            "dev": true
+        },
+        "supports-color": {
+            "version": "5.5.0",
+            "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+            "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+            "dev": true,
+            "requires": {
+                "has-flag": "^3.0.0"
+            }
+        },
+        "tapable": {
+            "version": "2.2.1",
+            "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
+            "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
+            "dev": true
+        },
+        "terser": {
+            "version": "5.8.0",
+            "resolved": "https://registry.npmjs.org/terser/-/terser-5.8.0.tgz",
+            "integrity": "sha512-f0JH+6yMpneYcRJN314lZrSwu9eKkUFEHLN/kNy8ceh8gaRiLgFPJqrB9HsXjhEGdv4e/ekjTOFxIlL6xlma8A==",
+            "dev": true,
+            "peer": true,
+            "requires": {
+                "commander": "^2.20.0",
+                "source-map": "~0.7.2",
+                "source-map-support": "~0.5.20"
+            },
+            "dependencies": {
+                "source-map": {
+                    "version": "0.7.3",
+                    "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
+                    "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
+                    "dev": true,
+                    "peer": true
+                }
+            }
+        },
+        "terser-webpack-plugin": {
+            "version": "5.2.4",
+            "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.2.4.tgz",
+            "integrity": "sha512-E2CkNMN+1cho04YpdANyRrn8CyN4yMy+WdFKZIySFZrGXZxJwJP6PMNGGc/Mcr6qygQHUUqRxnAPmi0M9f00XA==",
+            "dev": true,
+            "peer": true,
+            "requires": {
+                "jest-worker": "^27.0.6",
+                "p-limit": "^3.1.0",
+                "schema-utils": "^3.1.1",
+                "serialize-javascript": "^6.0.0",
+                "source-map": "^0.6.1",
+                "terser": "^5.7.2"
+            },
+            "dependencies": {
+                "source-map": {
+                    "version": "0.6.1",
+                    "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+                    "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+                    "dev": true,
+                    "peer": true
+                }
+            }
+        },
+        "test-value": {
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/test-value/-/test-value-2.1.0.tgz",
+            "integrity": "sha1-Edpv9nDzRxpztiXKTz/c97t0gpE=",
+            "dev": true,
+            "peer": true,
+            "requires": {
+                "array-back": "^1.0.3",
+                "typical": "^2.6.0"
+            },
+            "dependencies": {
+                "array-back": {
+                    "version": "1.0.4",
+                    "resolved": "https://registry.npmjs.org/array-back/-/array-back-1.0.4.tgz",
+                    "integrity": "sha1-ZEun8JX3/898Q7Xw3DnTwfA8Bjs=",
+                    "dev": true,
+                    "peer": true,
+                    "requires": {
+                        "typical": "^2.6.0"
+                    }
+                }
+            }
+        },
+        "to-regex-range": {
+            "version": "5.0.1",
+            "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+            "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+            "dev": true,
+            "requires": {
+                "is-number": "^7.0.0"
+            }
+        },
+        "traverse-chain": {
+            "version": "0.1.0",
+            "resolved": "https://registry.npmjs.org/traverse-chain/-/traverse-chain-0.1.0.tgz",
+            "integrity": "sha1-YdvC1Ttp/2CRoSoWj9fUMxB+QPE=",
+            "dev": true
+        },
+        "ts-essentials": {
+            "version": "7.0.3",
+            "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-7.0.3.tgz",
+            "integrity": "sha512-8+gr5+lqO3G84KdiTSMRLtuyJ+nTBVRKuCrK4lidMPdVeEp0uqC875uE5NMcaA7YYMN7XsNiFQuMvasF8HT/xQ==",
+            "dev": true,
+            "requires": {}
+        },
+        "ts-loader": {
+            "version": "9.2.5",
+            "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.2.5.tgz",
+            "integrity": "sha512-al/ATFEffybdRMUIr5zMEWQdVnCGMUA9d3fXJ8dBVvBlzytPvIszoG9kZoR+94k6/i293RnVOXwMaWbXhNy9pQ==",
+            "dev": true,
+            "requires": {
+                "chalk": "^4.1.0",
+                "enhanced-resolve": "^5.0.0",
+                "micromatch": "^4.0.0",
+                "semver": "^7.3.4"
+            },
+            "dependencies": {
+                "ansi-styles": {
+                    "version": "4.3.0",
+                    "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+                    "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+                    "dev": true,
+                    "requires": {
+                        "color-convert": "^2.0.1"
+                    }
+                },
+                "chalk": {
+                    "version": "4.1.2",
+                    "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+                    "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+                    "dev": true,
+                    "requires": {
+                        "ansi-styles": "^4.1.0",
+                        "supports-color": "^7.1.0"
+                    }
+                },
+                "color-convert": {
+                    "version": "2.0.1",
+                    "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+                    "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+                    "dev": true,
+                    "requires": {
+                        "color-name": "~1.1.4"
+                    }
+                },
+                "color-name": {
+                    "version": "1.1.4",
+                    "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+                    "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+                    "dev": true
+                },
+                "has-flag": {
+                    "version": "4.0.0",
+                    "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+                    "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+                    "dev": true
+                },
+                "semver": {
+                    "version": "7.3.5",
+                    "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
+                    "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
+                    "dev": true,
+                    "requires": {
+                        "lru-cache": "^6.0.0"
+                    }
+                },
+                "supports-color": {
+                    "version": "7.2.0",
+                    "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+                    "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+                    "dev": true,
+                    "requires": {
+                        "has-flag": "^4.0.0"
+                    }
+                }
+            }
+        },
+        "ts-poet": {
+            "version": "4.5.0",
+            "resolved": "https://registry.npmjs.org/ts-poet/-/ts-poet-4.5.0.tgz",
+            "integrity": "sha512-Vs2Zsiz3zf5qdFulFTIEpaLdgWeHXKh+4pv+ycVqEh+ZuUOVGrN0i9lbxVx7DB1FBogExytz3OuaBMJfWffpSQ==",
+            "requires": {
+                "@types/prettier": "^1.19.0",
+                "lodash": "^4.17.15",
+                "prettier": "^2.0.2"
+            },
+            "dependencies": {
+                "@types/prettier": {
+                    "version": "1.19.1",
+                    "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-1.19.1.tgz",
+                    "integrity": "sha512-5qOlnZscTn4xxM5MeGXAMOsIOIKIbh9e85zJWfBRVPlRMEVawzoPhINYbRGkBZCI8LxvBe7tJCdWiarA99OZfQ=="
+                }
+            }
+        },
+        "ts-proto": {
+            "version": "1.83.1",
+            "resolved": "https://registry.npmjs.org/ts-proto/-/ts-proto-1.83.1.tgz",
+            "integrity": "sha512-pAB7FKIqMKjTnakvMyBB7VeIeXPl+3YPWGfp03laSRf7tGTtrt8xV9jyrLB1WU5vSzusYbz57kBCt4lqbZULqw==",
+            "requires": {
+                "@types/object-hash": "^1.3.0",
+                "dataloader": "^1.4.0",
+                "object-hash": "^1.3.1",
+                "protobufjs": "^6.8.8",
+                "ts-poet": "^4.5.0",
+                "ts-proto-descriptors": "^1.2.1"
+            }
+        },
+        "ts-proto-descriptors": {
+            "version": "1.3.1",
+            "resolved": "https://registry.npmjs.org/ts-proto-descriptors/-/ts-proto-descriptors-1.3.1.tgz",
+            "integrity": "sha512-Cybb3fqceMwA6JzHdC32dIo8eVGVmXrM6TWhdk1XQVVHT/6OQqk0ioyX1dIdu3rCIBhRmWUhUE4HsyK+olmgMw==",
+            "requires": {
+                "long": "^4.0.0",
+                "protobufjs": "^6.8.8"
+            }
+        },
+        "tslib": {
+            "version": "1.14.1",
+            "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+            "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
+            "dev": true
+        },
+        "tslint": {
+            "version": "6.1.3",
+            "resolved": "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz",
+            "integrity": "sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg==",
+            "dev": true,
+            "requires": {
+                "@babel/code-frame": "^7.0.0",
+                "builtin-modules": "^1.1.1",
+                "chalk": "^2.3.0",
+                "commander": "^2.12.1",
+                "diff": "^4.0.1",
+                "glob": "^7.1.1",
+                "js-yaml": "^3.13.1",
+                "minimatch": "^3.0.4",
+                "mkdirp": "^0.5.3",
+                "resolve": "^1.3.2",
+                "semver": "^5.3.0",
+                "tslib": "^1.13.0",
+                "tsutils": "^2.29.0"
+            }
+        },
+        "tslint-config-prettier": {
+            "version": "1.18.0",
+            "resolved": "https://registry.npmjs.org/tslint-config-prettier/-/tslint-config-prettier-1.18.0.tgz",
+            "integrity": "sha512-xPw9PgNPLG3iKRxmK7DWr+Ea/SzrvfHtjFt5LBl61gk2UBG/DB9kCXRjv+xyIU1rUtnayLeMUVJBcMX8Z17nDg==",
+            "dev": true
+        },
+        "tsutils": {
+            "version": "2.29.0",
+            "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz",
+            "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==",
+            "dev": true,
+            "requires": {
+                "tslib": "^1.8.1"
+            }
+        },
+        "typechain": {
+            "version": "5.1.2",
+            "resolved": "https://registry.npmjs.org/typechain/-/typechain-5.1.2.tgz",
+            "integrity": "sha512-FuaCxJd7BD3ZAjVJoO+D6TnqKey3pQdsqOBsC83RKYWKli5BDhdf0TPkwfyjt20TUlZvOzJifz+lDwXsRkiSKA==",
+            "dev": true,
+            "peer": true,
+            "requires": {
+                "@types/prettier": "^2.1.1",
+                "command-line-args": "^4.0.7",
+                "debug": "^4.1.1",
+                "fs-extra": "^7.0.0",
+                "glob": "^7.1.6",
+                "js-sha3": "^0.8.0",
+                "lodash": "^4.17.15",
+                "mkdirp": "^1.0.4",
+                "prettier": "^2.1.2",
+                "ts-essentials": "^7.0.1"
+            },
+            "dependencies": {
+                "js-sha3": {
+                    "version": "0.8.0",
+                    "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz",
+                    "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==",
+                    "dev": true,
+                    "peer": true
+                },
+                "mkdirp": {
+                    "version": "1.0.4",
+                    "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
+                    "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
+                    "dev": true,
+                    "peer": true
+                }
+            }
+        },
+        "typescript": {
+            "version": "4.4.2",
+            "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.2.tgz",
+            "integrity": "sha512-gzP+t5W4hdy4c+68bfcv0t400HVJMMd2+H9B7gae1nQlBzCqvrXX+6GL/b3GAgyTH966pzrZ70/fRjwAtZksSQ==",
+            "dev": true
+        },
+        "typical": {
+            "version": "2.6.1",
+            "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz",
+            "integrity": "sha1-XAgOXWYcu+OCWdLnCjxyU+hziB0=",
+            "dev": true,
+            "peer": true
+        },
+        "universalify": {
+            "version": "0.1.2",
+            "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
+            "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
+            "dev": true,
+            "peer": true
+        },
+        "uri-js": {
+            "version": "4.4.1",
+            "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+            "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+            "dev": true,
+            "peer": true,
+            "requires": {
+                "punycode": "^2.1.0"
+            }
+        },
+        "utf-8-validate": {
+            "version": "5.0.5",
+            "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.5.tgz",
+            "integrity": "sha512-+pnxRYsS/axEpkrrEpzYfNZGXp0IjC/9RIxwM5gntY4Koi8SHmUGSfxfWqxZdRxrtaoVstuOzUp/rbs3JSPELQ==",
+            "dev": true,
+            "optional": true,
+            "peer": true,
+            "requires": {
+                "node-gyp-build": "^4.2.0"
+            }
+        },
+        "watchpack": {
+            "version": "2.2.0",
+            "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.2.0.tgz",
+            "integrity": "sha512-up4YAn/XHgZHIxFBVCdlMiWDj6WaLKpwVeGQk2I5thdYxF/KmF0aaz6TfJZ/hfl1h/XlcDr7k1KH7ThDagpFaA==",
+            "dev": true,
+            "peer": true,
+            "requires": {
+                "glob-to-regexp": "^0.4.1",
+                "graceful-fs": "^4.1.2"
+            }
+        },
+        "webpack": {
+            "version": "5.52.1",
+            "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.52.1.tgz",
+            "integrity": "sha512-wkGb0hLfrS7ML3n2xIKfUIwHbjB6gxwQHyLmVHoAqEQBw+nWo+G6LoHL098FEXqahqximsntjBLuewStrnJk0g==",
+            "dev": true,
+            "peer": true,
+            "requires": {
+                "@types/eslint-scope": "^3.7.0",
+                "@types/estree": "^0.0.50",
+                "@webassemblyjs/ast": "1.11.1",
+                "@webassemblyjs/wasm-edit": "1.11.1",
+                "@webassemblyjs/wasm-parser": "1.11.1",
+                "acorn": "^8.4.1",
+                "acorn-import-assertions": "^1.7.6",
+                "browserslist": "^4.14.5",
+                "chrome-trace-event": "^1.0.2",
+                "enhanced-resolve": "^5.8.0",
+                "es-module-lexer": "^0.7.1",
+                "eslint-scope": "5.1.1",
+                "events": "^3.2.0",
+                "glob-to-regexp": "^0.4.1",
+                "graceful-fs": "^4.2.4",
+                "json-parse-better-errors": "^1.0.2",
+                "loader-runner": "^4.2.0",
+                "mime-types": "^2.1.27",
+                "neo-async": "^2.6.2",
+                "schema-utils": "^3.1.0",
+                "tapable": "^2.1.1",
+                "terser-webpack-plugin": "^5.1.3",
+                "watchpack": "^2.2.0",
+                "webpack-sources": "^3.2.0"
+            }
+        },
+        "webpack-sources": {
+            "version": "3.2.0",
+            "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.0.tgz",
+            "integrity": "sha512-fahN08Et7P9trej8xz/Z7eRu8ltyiygEo/hnRi9KqBUs80KeDcnf96ZJo++ewWd84fEf3xSX9bp4ZS9hbw0OBw==",
+            "dev": true,
+            "peer": true
+        },
+        "wrappy": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+            "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+            "dev": true
+        },
+        "ws": {
+            "version": "7.4.6",
+            "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz",
+            "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==",
+            "dev": true,
+            "requires": {}
+        },
+        "yallist": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+            "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+            "dev": true
+        },
+        "yocto-queue": {
+            "version": "0.1.0",
+            "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+            "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+            "dev": true,
+            "peer": true
+        }
+    }
+}

+ 53 - 0
third_party/pyth/p2w-relay/package.json

@@ -0,0 +1,53 @@
+{
+    "name": "@certusone/p2w-relay",
+    "version": "0.1.0",
+    "description": "p2w-sdk integration test; not intended for production use",
+    "private": true,
+    "types": "lib/index.d.ts",
+    "main": "lib/index.js",
+    "files": [
+        "lib/**/*"
+    ],
+    "scripts": {
+      "start": "node -r esm lib/index.js",
+        "build": "npm run build-eth-types && npm run build-lib",
+        "build-lib": "npm run copy-artifacts && tsc",
+        "build-watch": "npm run build-eth-types && npm run copy-artifacts && tsc --watch",
+	"build-eth-types": "node scripts/copyEthContracts.cjs && typechain --target=ethers-v5 --out-dir=src/ethers-contracts contracts/*.json",
+        "copy-artifacts": "node scripts/copyWasm.cjs && node scripts/copyEthersTypes.cjs",
+        "lint": "tslint -p tsconfig.json",
+        "postversion": "git push && git push --tags",
+        "preversion": "npm run lint",
+        "version": "npm run format && git add -A src"
+    },
+    "repository": {
+        "type": "git",
+        "url": "git+https://github.com/certusone/wormhole.git"
+    },
+    "author": "https://certus.one",
+    "license": "MIT",
+    "devDependencies": {
+        "@openzeppelin/contracts": "^4.2.0",
+        "@typechain/ethers-v5": "^7.1.2",
+        "@types/long": "^4.0.1",
+        "@types/node": "^16.6.1",
+        "copy-dir": "^1.3.0",
+        "esm": "^3.2.25",
+        "ethers": "^5.4.7",
+        "find": "^0.3.0",
+        "prettier": "^2.3.2",
+        "ts-loader": "^9.2.5",
+        "tslint": "^6.1.3",
+        "tslint-config-prettier": "^1.18.0",
+        "typescript": "^4.3.5"
+    },
+    "dependencies": {
+        "@certusone/p2w-sdk": "file:../p2w-sdk",
+        "@certusone/wormhole-sdk": "file:../../../sdk/js",
+        "@improbable-eng/grpc-web-node-http-transport": "^0.14.1"
+    },
+    "bugs": {
+        "url": "https://github.com/certusone/wormhole/issues"
+    },
+    "homepage": "https://github.com/certusone/wormhole#readme"
+}

+ 2 - 0
third_party/pyth/p2w-relay/scripts/copyEthContracts.cjs

@@ -0,0 +1,2 @@
+const copydir = require("copy-dir");
+copydir.sync("../../../ethereum/build/contracts", "./contracts");

+ 17 - 0
third_party/pyth/p2w-relay/scripts/copyEthersTypes.cjs

@@ -0,0 +1,17 @@
+const find = require("find");
+const fs = require("fs");
+const path = require("path");
+
+const SOURCE_ROOT = "src";
+const TARGET_ROOT = "lib";
+
+find.eachfile(/\.d\.ts(\..*)?/, SOURCE_ROOT, fname => {
+
+    fname_copy = fname.replace(SOURCE_ROOT, TARGET_ROOT);
+
+    console.log("copying types:", fname, "to", fname_copy);
+
+    fs.mkdirSync(path.dirname(fname_copy), {recursive: true});
+
+    fs.copyFileSync(fname, fname_copy);
+});

+ 17 - 0
third_party/pyth/p2w-relay/scripts/copyWasm.cjs

@@ -0,0 +1,17 @@
+const find = require("find");
+const fs = require("fs");
+const path = require("path");
+
+const SOURCE_ROOT = "src";
+const TARGET_ROOT = "lib";
+
+find.eachfile(/\.wasm(\..*)?/, SOURCE_ROOT, fname => {
+
+    fname_copy = fname.replace(SOURCE_ROOT, TARGET_ROOT);
+
+    console.log("copyWasm:", fname, "to", fname_copy);
+
+    fs.mkdirSync(path.dirname(fname_copy), {recursive: true});
+
+    fs.copyFileSync(fname, fname_copy);
+});

+ 198 - 0
third_party/pyth/p2w-relay/src/index.ts

@@ -0,0 +1,198 @@
+import { NodeHttpTransport } from "@improbable-eng/grpc-web-node-http-transport";
+import {PythImplementation__factory} from "./ethers-contracts";
+
+import * as http from "http";
+import * as net from "net";
+import fs from "fs";
+
+
+import {ethers} from "ethers";
+
+import {getSignedAttestation, parseBatchAttestation, p2w_core, sol_addr2buf} from "@certusone/p2w-sdk";
+
+import {setDefaultWasm, importCoreWasm} from "@certusone/wormhole-sdk/lib/cjs/solana/wasm";
+
+interface NewAttestationsResponse {
+    pendingSeqnos: Array<number>,
+}
+
+
+async function readinessProbeRoutine(port: number) {
+    let srv = net.createServer();
+
+    return await srv.listen(port);
+}
+
+(async () => {
+
+    // p2w-attest exposes an HTTP endpoint that shares the currently pending sequence numbers
+    const P2W_ATTESTATIONS_HOST = process.env.P2W_ATTESTATIONS_HOST || "p2w-attest";
+    const P2W_ATTESTATIONS_PORT = Number(process.env.P2W_ATTESTATIONS_PORT || "4343");
+    const P2W_ATTESTATIONS_POLL_INTERVAL_MS = Number(process.env.P2W_ATTESTATIONS_POLL_INTERVAL_MS || "5000");
+
+    const P2W_SOL_ADDRESS = process.env.P2W_SOL_ADDRESS || "P2WH424242424242424242424242424242424242424";
+
+    const READINESS_PROBE_PORT = Number(process.env.READINESS_PROBE_PORT || "2000");
+
+    const P2W_RELAY_RETRY_COUNT = Number(process.env.P2W_RELAY_RETRY_COUNT || "3");
+
+    // ETH node connection details; Currently, we expect to read BIP44
+    // wallet recovery mnemonics from a text file.
+    const ETH_NODE_URL = process.env.ETH_NODE_URL || "ws://eth-devnet:8545";
+    const ETH_P2W_CONTRACT = process.env.ETH_P2W_CONTRACT || "0xA94B7f0465E98609391C623d0560C5720a3f2D33";
+    const ETH_MNEMONIC_FILE = process.env.ETH_MNEMONIC_FILE || "../../../ethereum/devnet_mnemonic.txt";
+    const ETH_HD_WALLET_PATH = process.env.ETH_HD_WALLET_PATH || "m/44'/60'/0'/0/0";
+
+    // Public RPC address for use with signed attestation queries
+    const GUARDIAN_RPC_HOST_PORT = process.env.GUARDIAN_RPC_HOST_PORT || "http://guardian:7071";
+
+    let readinessProbe = null;
+
+    let seqnoPool: Map<number, number> = new Map();
+
+    console.log(`Polling attestations endpoint every ${P2W_ATTESTATIONS_POLL_INTERVAL_MS / 1000} seconds`);
+
+    setDefaultWasm("node");
+    const {parse_vaa} = await importCoreWasm();
+
+    let p2w_eth: any;
+
+    // Connect to ETH
+    try {
+	let provider = new ethers.providers.WebSocketProvider(ETH_NODE_URL);
+	let mnemonic: string = fs.readFileSync(ETH_MNEMONIC_FILE).toString("utf-8").trim();
+	let wallet = ethers.Wallet.fromMnemonic(mnemonic, ETH_HD_WALLET_PATH);
+	console.log(`Using ETH wallet pubkey: ${wallet.publicKey}`);
+	let signer = new ethers.Wallet(wallet.privateKey, provider);
+	let balance = await signer.getBalance();
+	console.log(`Account balance is ${balance}`);
+	let factory = new PythImplementation__factory(signer);
+	p2w_eth = factory.attach(ETH_P2W_CONTRACT);
+    }
+    catch(e) {
+	console.error(`Error: Could not instantiate ETH contract:`, e);
+	throw e;
+    }
+
+    while (true) {
+	http.get({
+	    hostname: P2W_ATTESTATIONS_HOST,
+	    port: P2W_ATTESTATIONS_PORT,
+	    path: "/",
+	    agent: false
+	}, (res) => {
+	    if (res.statusCode != 200) {
+		console.error("Could not reach attestations endpoint", res);
+	    } else {
+		let chunks: string[] = [];
+		res.setEncoding("utf-8");
+
+		res.on('data', (chunk) => {
+		    chunks.push(chunk);
+		});
+
+		res.on('end', () => {
+		    let body = chunks.join('');
+
+		    let response: NewAttestationsResponse = JSON.parse(body);
+
+		    console.log(`Got ${response.pendingSeqnos.length} new seqnos: ${response.pendingSeqnos}`);
+
+		    for (let seqno of response.pendingSeqnos) {
+			seqnoPool.set(seqno, 0);
+		    }
+		});
+	    }
+	}).on('error', (e) => {
+	    console.error(`Got error: ${e.message}`);
+	});
+
+	console.log("Processing seqnos:", seqnoPool);
+	for (let poolEntry of seqnoPool) {
+
+	    let seqno = poolEntry[0];
+	    let attempts = poolEntry[1];
+
+	    if (attempts >= P2W_RELAY_RETRY_COUNT) {
+		console.warn(`[seqno ${poolEntry}] Exceeded retry count, removing from list`);
+		seqnoPool.delete(seqno);
+		continue;
+	    }
+
+	    let vaaResponse: any;
+	    try {
+		vaaResponse = await getSignedAttestation(
+		    GUARDIAN_RPC_HOST_PORT,
+		    P2W_SOL_ADDRESS,
+		    seqno,
+		    {
+			transport: NodeHttpTransport()
+		    }
+		);
+	    }
+	    catch(e) {
+		console.error(`[seqno ${poolEntry}] Error: Could not call getSignedAttestation:`, e);
+
+		seqnoPool.set(seqno, attempts + 1);
+
+		continue;
+	    }
+
+	    console.log(`[seqno ${poolEntry}] Price attestation VAA bytes:\n`, vaaResponse.vaaBytes);
+
+	    let parsedVaa = parse_vaa(vaaResponse.vaaBytes);
+
+	    console.log(`[seqno ${poolEntry}] Parsed VAA:\n`, parsedVaa);
+
+	    let parsedAttestations = await parseBatchAttestation(parsedVaa.payload);
+
+	    console.log(`[seqno ${poolEntry}] Parsed ${parsedAttestations.length} price attestations:\n`, parsedAttestations);
+
+	    // try {
+	    // 	let tx = await p2w_eth.attestPrice(vaaResponse.vaaBytes, {gasLimit: 1000000});
+	    // 	let retval = await tx.wait();
+	    // 	console.log(`[seqno ${poolEntry}] attestPrice() output:\n`, retval);
+	    // } catch(e) {
+	    // 	console.error(`[seqno ${poolEntry}, {parsedAttestations.length} symbols] Error: Could not call attestPrice() on ETH:`, e);
+
+	    // 	seqnoPool.set(seqno, attempts + 1);
+
+	    // 	continue;
+	    // }
+
+	    console.warn("TODO: implement relayer ETH call");
+
+	    // for (let att of parsedAttestations) {
+
+	    // 	let product_id = att.product_id;
+	    // 	let price_type = att.price_type == "Price" ? 1 : 0;
+	    // 	let latest_attestation: any;
+	    // 	try {
+	    // 	    let p2w = await p2w_core();
+
+	    // 	    console.log(`Looking up latestAttestation for `, product_id, price_type);
+
+	    // 	    latest_attestation = await p2w_eth.latestAttestation(product_id, price_type);
+	    // 	} catch(e) {
+	    // 	    console.error(`[seqno ${poolEntry}] Error: Could not call latestAttestation() on ETH:`, e);
+
+	    // 	    seqnoPool.set(seqno, attempts + 1);
+
+	    // 	    continue;
+	    // 	}
+
+	    // 	console.log(`[seqno ${poolEntry}] Latest price type ${price_type} attestation of ${product_id} is ${latest_attestation}`);
+	    // }
+
+	    if (!readinessProbe) {
+		console.log(`[seqno ${poolEntry}] Attestation successful. Starting readiness probe.`);
+		readinessProbe = readinessProbeRoutine(READINESS_PROBE_PORT);
+	    }
+
+	    seqnoPool.delete(seqno); // Everything went well, seqno no longer pending.
+	}
+
+	await new Promise(f => {setTimeout(f, P2W_ATTESTATIONS_POLL_INTERVAL_MS);});
+    }
+
+})();

+ 15 - 0
third_party/pyth/p2w-relay/tsconfig.json

@@ -0,0 +1,15 @@
+{
+    "compilerOptions": {
+	"target": "esnext",
+	"module": "esnext",
+	"moduleResolution": "node",
+	"declaration": true,
+	"outDir": "./lib",
+	"strict": true,
+	"esModuleInterop": true,
+	"downlevelIteration": true,
+	"allowJs": true,
+    },
+    "include": ["src", "types"],
+    "exclude": ["node_modules", "**/__tests__/*"]
+}

+ 9 - 0
third_party/pyth/p2w-relay/tslint.json

@@ -0,0 +1,9 @@
+
+{
+  "extends": ["tslint:recommended", "tslint-config-prettier"],
+  "linterOptions": {
+    "exclude": [
+      "src/proto/**"
+    ]
+  }
+}

+ 1 - 1
third_party/pyth/p2w-sdk/.gitignore

@@ -24,7 +24,7 @@ yarn-error.log*
 
 # ethereum contracts
 /contracts
-/src/ethers-contracts
+/src/*-contracts/
 
 # tsproto output
 /src/proto

+ 10 - 3
third_party/pyth/p2w-sdk/README.md

@@ -2,7 +2,14 @@
 This project contains a library for interacting with pyth2wormhole and adjacent APIs.
 
 # Install
-Firstly, please follow instructions in `//bridge_ui/README.md` where
-`//` is the Wormhole project root.
+For now, the in-house dependencies are referenced by relative
+path. The commands below will build those. For an automated version of
+this process, please refer to `p2w-relay`'s Dockerfile and/or our [Tilt](https://tilt.dev)
+devnet with `pyth` enabled.
 
-# Usage
+```shell
+# Run the commands in this README's directory for --prefix to work
+$ npm --prefix ../../../ethereum ci && npm --prefix ../../../ethereum run build # ETH contracts
+$ npm --prefix ../../../sdk/js ci # Wormhole SDK
+$ npm ci && npm run build # Pyth2wormhole SDK
+```

File diff suppressed because it is too large
+ 527 - 159
third_party/pyth/p2w-sdk/package-lock.json


+ 11 - 9
third_party/pyth/p2w-sdk/package.json

@@ -8,12 +8,14 @@
         "lib/**/*"
     ],
     "scripts": {
-        "build": "tsc && node scripts/copyWasm.js",
-        "build-test": "webpack",
+        "build": "npm run build-eth-types && npm run build-lib",
+        "build-eth-types": "node scripts/copyEthContracts.cjs && typechain --target=ethers-v5 --out-dir=src/ethers-contracts contracts/*.json",
+        "build-lib": "npm run copy-artifacts && tsc",
+        "build-watch": "npm run copy-artifacts && tsc --watch",
+        "copy-artifacts": "node scripts/copyWasm.cjs && node scripts/copyEthersTypes.cjs",
         "lint": "tslint -p tsconfig.json",
         "postversion": "git push && git push --tags",
         "preversion": "npm run lint",
-        "test": "node lib/test.js",
         "version": "npm run format && git add -A src"
     },
     "repository": {
@@ -24,22 +26,22 @@
     "license": "MIT",
     "devDependencies": {
         "@openzeppelin/contracts": "^4.2.0",
-        "@typechain/ethers-v5": "^7.0.1",
+        "@typechain/ethers-v5": "^7.1.2",
         "@types/long": "^4.0.1",
         "@types/node": "^16.6.1",
         "copy-dir": "^1.3.0",
-        "ethers": "^5.4.4",
         "find": "^0.3.0",
         "prettier": "^2.3.2",
-        "ts-loader": "^9.2.5",
         "tslint": "^6.1.3",
         "tslint-config-prettier": "^1.18.0",
-        "typescript": "^4.3.5",
-        "webpack-cli": "^4.8.0"
+        "typescript": "^4.3.5"
+    },
+    "peerDependencies": {
+	"@solana/web3.js": "^1.24.0"
     },
     "dependencies": {
         "@certusone/wormhole-sdk": "file:../../../sdk/js",
-        "@solana/web3.js": "^1.26.0"
+        "@improbable-eng/grpc-web-node-http-transport": "^0.14.1"
     },
     "bugs": {
         "url": "https://github.com/certusone/wormhole/issues"

+ 2 - 0
third_party/pyth/p2w-sdk/scripts/copyEthContracts.cjs

@@ -0,0 +1,2 @@
+const copydir = require("copy-dir");
+copydir.sync("../../../ethereum/build/contracts", "./contracts");

+ 17 - 0
third_party/pyth/p2w-sdk/scripts/copyEthersTypes.cjs

@@ -0,0 +1,17 @@
+const find = require("find");
+const fs = require("fs");
+const path = require("path");
+
+const SOURCE_ROOT = "src";
+const TARGET_ROOT = "lib";
+
+find.eachfile(/\.d\.ts(\..*)?/, SOURCE_ROOT, fname => {
+
+    fname_copy = fname.replace(SOURCE_ROOT, TARGET_ROOT);
+
+    console.log("copying types:", fname, "to", fname_copy);
+
+    fs.mkdirSync(path.dirname(fname_copy), {recursive: true});
+
+    fs.copyFileSync(fname, fname_copy);
+});

+ 17 - 0
third_party/pyth/p2w-sdk/scripts/copyWasm.cjs

@@ -0,0 +1,17 @@
+const find = require("find");
+const fs = require("fs");
+const path = require("path");
+
+const SOURCE_ROOT = "src";
+const TARGET_ROOT = "lib";
+
+find.eachfile(/\.wasm(\..*)?/, SOURCE_ROOT, fname => {
+
+    fname_copy = fname.replace(SOURCE_ROOT, TARGET_ROOT);
+
+    console.log("copyWasm:", fname, "to", fname_copy);
+
+    fs.mkdirSync(path.dirname(fname_copy), {recursive: true});
+
+    fs.copyFileSync(fname, fname_copy);
+});

+ 0 - 14
third_party/pyth/p2w-sdk/scripts/copyWasm.js

@@ -1,14 +0,0 @@
-const find = require("find");
-const fs = require("fs");
-
-const SOURCE_ROOT = "src";
-const TARGET_ROOT = "lib";
-
-find.eachfile(/\.wasm(\..*)?/, SOURCE_ROOT, file => {
-
-    copy = file.replace(SOURCE_ROOT, TARGET_ROOT);
-
-    console.log("copyWasm:", file, "to", copy);
-
-    fs.copyFileSync(file, copy);
-});

+ 39 - 7
third_party/pyth/p2w-sdk/src/index.ts

@@ -1,10 +1,42 @@
-// import {Connection, PublicKey, SystemProgram} from "@solana/web3.js";
-import { ixFromRust} from "@certusone/wormhole-sdk";
+import { getSignedVAA, CHAIN_ID_SOLANA} from "@certusone/wormhole-sdk";
+import { zeroPad } from "ethers/lib/utils";
+import { PublicKey} from "@solana/web3.js";
 
-async function p2wHello() {
-    const p2w = await import("./solana/p2w-core/pyth2wormhole");
-    let s = p2w.hello_p2w();
-    console.log(s);
+var P2W_INSTANCE: any = undefined;
+
+// Import p2w wasm bindings; be smart about it
+export async function p2w_core(): Promise<any> {
+    // Only import once if P2W wasm is needed
+    if (!P2W_INSTANCE) {
+	P2W_INSTANCE = await import("./solana/p2w-core/pyth2wormhole");
+    }
+    return P2W_INSTANCE;
+}
+
+export function sol_addr2buf(addr: string): Buffer {
+    return Buffer.from(zeroPad(new PublicKey(addr).toBytes(), 32));
+}
+
+
+export async function getSignedAttestation(host: string, p2w_addr: string, sequence: number, extraGrpcOpts = {}): Promise<any>
+{
+    const p2w = await p2w_core();
+    let emitter = p2w.get_emitter_address(p2w_addr);
+
+    let emitterHex = sol_addr2buf(emitter).toString("hex");
+    return await getSignedVAA(host, CHAIN_ID_SOLANA, emitterHex, "" + sequence, extraGrpcOpts);
 }
 
-p2wHello();
+export async function parseAttestation(vaa_payload: Uint8Array): Promise<any> {
+    const p2w = await p2w_core();
+
+    return await p2w.parse_attestation(vaa_payload);
+}
+
+export async function parseBatchAttestation(vaa_payload: Uint8Array): Promise<any> {
+    const p2w = await p2w_core();
+
+    console.log("p2w.parse_batch_attestaion is", p2w.parse_batch_attestation);
+
+    return await p2w.parse_batch_attestation(vaa_payload);
+}

+ 2 - 0
third_party/pyth/p2w-sdk/tsconfig.json

@@ -6,10 +6,12 @@
 	"declaration": true,
 	"outDir": "./lib",
 	"strict": true,
+	"skipLibCheck": true,
 	"esModuleInterop": true,
 	"downlevelIteration": true,
 	"allowJs": true,
     },
+    "types": [],
     "include": ["src", "types"],
     "exclude": ["node_modules", "**/__tests__/*"]
 }

+ 0 - 26
third_party/pyth/p2w-sdk/webpack.config.js

@@ -1,26 +0,0 @@
-const path = require('path');
-
-module.exports = {
-  entry: './src/index.ts',
-  experiments: {
-    asyncWebAssembly: true,
-  },
-  mode: 'development',
-  target: 'node',
-  module: {
-    rules: [
-      {
-        test: /\.tsx?$/,
-        use: 'ts-loader',
-        exclude: /node_modules/,
-      },
-    ],
-  },
-  resolve: {
-    extensions: ['.tsx', '.ts', '.js'],
-  },
-  output: {
-    filename: 'test.js',
-    path: path.resolve(__dirname, 'lib'),
-  },
-};

+ 181 - 118
third_party/pyth/p2w_autoattest.py

@@ -1,44 +1,56 @@
 #!/usr/bin/env python3
 
 # This script sets up a simple loop for periodical attestation of Pyth data
-from pyth_utils import *
-
-from http.client import HTTPConnection
-from http.server import HTTPServer, BaseHTTPRequestHandler
-
 import json
+import logging
 import os
 import re
-import subprocess
-import time
+import sys
 import threading
+import time
+from http.client import HTTPConnection
+from http.server import BaseHTTPRequestHandler, HTTPServer
 
+from pyth_utils import *
 
-P2W_ADDRESS = "P2WH424242424242424242424242424242424242424"
+logging.basicConfig(
+    level=logging.DEBUG, format="%(asctime)s | %(module)s | %(levelname)s | %(message)s"
+)
+
+P2W_SOL_ADDRESS = os.environ.get(
+    "P2W_SOL_ADDRESS", "P2WH424242424242424242424242424242424242424"
+)
 P2W_ATTEST_INTERVAL = float(os.environ.get("P2W_ATTEST_INTERVAL", 5))
 P2W_OWNER_KEYPAIR = os.environ.get(
-    "P2W_OWNER_KEYPAIR", f"/usr/src/solana/keys/p2w_owner.json")
+    "P2W_OWNER_KEYPAIR", "/usr/src/solana/keys/p2w_owner.json"
+)
 P2W_ATTESTATIONS_PORT = int(os.environ.get("P2W_ATTESTATIONS_PORT", 4343))
+P2W_INITIALIZE_SOL_CONTRACT = os.environ.get("P2W_INITIALIZE_SOL_CONTRACT", None)
+
+PYTH_TEST_ACCOUNTS_HOST = "pyth"
+PYTH_TEST_ACCOUNTS_PORT = 4242
 
-PYTH_ACCOUNTS_HOST = "pyth"
-PYTH_ACCOUNTS_PORT = 4242
+P2W_ATTESTATION_CFG = os.environ.get("P2W_ATTESTATION_CFG", None)
 
-WORMHOLE_ADDRESS = "Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o"
+WORMHOLE_ADDRESS = os.environ.get(
+    "WORMHOLE_ADDRESS", "Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o"
+)
 
 ATTESTATIONS = {
     "pendingSeqnos": [],
 }
 
+
 class P2WAutoattestStatusEndpoint(BaseHTTPRequestHandler):
     """
     A dumb endpoint for last attested price metadata.
     """
 
     def do_GET(self):
-        print(f"Got path {self.path}")
+        logging.info(f"Got path {self.path}")
         sys.stdout.flush()
         data = json.dumps(ATTESTATIONS).encode("utf-8")
-        print(f"Sending:\n{data}")
+        logging.debug(f"Sending: {data}")
 
         ATTESTATIONS["pendingSeqnos"] = []
 
@@ -49,91 +61,144 @@ class P2WAutoattestStatusEndpoint(BaseHTTPRequestHandler):
         self.wfile.write(data)
         self.wfile.flush()
 
+
 def serve_attestations():
     """
     Run a barebones HTTP server to share Pyth2wormhole attestation history
     """
-    server_address = ('', P2W_ATTESTATIONS_PORT)
+    server_address = ("", P2W_ATTESTATIONS_PORT)
     httpd = HTTPServer(server_address, P2WAutoattestStatusEndpoint)
     httpd.serve_forever()
 
+if SOL_AIRDROP_AMT > 0:
+    # Fund the p2w owner
+    sol_run_or_die("airdrop", [
+        str(SOL_AIRDROP_AMT),
+        "--keypair", P2W_OWNER_KEYPAIR,
+        "--commitment", "finalized",
+    ])
+    
+
+if P2W_INITIALIZE_SOL_CONTRACT is not None:
+    # Get actor pubkeys
+    P2W_OWNER_ADDRESS = sol_run_or_die(
+        "address", ["--keypair", P2W_OWNER_KEYPAIR], capture_output=True
+    ).stdout.strip()
+    PYTH_OWNER_ADDRESS = sol_run_or_die(
+        "address", ["--keypair", PYTH_PROGRAM_KEYPAIR], capture_output=True
+    ).stdout.strip()
+
+    init_result = run_or_die(
+        [
+            "pyth2wormhole-client",
+            "--log-level",
+            "4",
+            "--p2w-addr",
+            P2W_SOL_ADDRESS,
+            "--rpc-url",
+            SOL_RPC_URL,
+            "--payer",
+            P2W_OWNER_KEYPAIR,
+            "init",
+            "--wh-prog",
+            WORMHOLE_ADDRESS,
+            "--owner",
+            P2W_OWNER_ADDRESS,
+            "--pyth-owner",
+            PYTH_OWNER_ADDRESS,
+        ],
+        capture_output=True,
+        die=False,
+    )
+
+    if init_result.returncode != 0:
+        logging.error(
+            "NOTE: pyth2wormhole-client init failed, retrying with set_config"
+        )
+        run_or_die(
+            [
+                "pyth2wormhole-client",
+                "--log-level",
+                "4",
+                "--p2w-addr",
+                P2W_SOL_ADDRESS,
+                "--rpc-url",
+                SOL_RPC_URL,
+                "--payer",
+                P2W_OWNER_KEYPAIR,
+                "set-config",
+                "--owner",
+                P2W_OWNER_KEYPAIR,
+                "--new-owner",
+                P2W_OWNER_ADDRESS,
+                "--new-wh-prog",
+                WORMHOLE_ADDRESS,
+                "--new-pyth-owner",
+                PYTH_OWNER_ADDRESS,
+            ],
+            capture_output=True,
+        )
+
+# Retrieve available symbols from the test pyth publisher if not provided in envs
+if P2W_ATTESTATION_CFG is None:
+    P2W_ATTESTATION_CFG = "./attestation_cfg_test.yaml"
+    conn = HTTPConnection(PYTH_TEST_ACCOUNTS_HOST, PYTH_TEST_ACCOUNTS_PORT)
+
+    conn.request("GET", "/")
+
+    res = conn.getresponse()
+
+    pyth_accounts = None
+
+    if res.getheader("Content-Type") == "application/json":
+        pyth_accounts = json.load(res)
+    else:
+        logging.error("Bad Content type")
+        sys.exit(1)
+
+    cfg_yaml = f"""
+---
+symbols:"""
+
+    logging.info(f"Retrieved {len(pyth_accounts)} Pyth accounts from endpoint: {pyth_accounts}")
 
-# Get actor pubkeys
-P2W_OWNER_ADDRESS = sol_run_or_die(
-    "address", ["--keypair", P2W_OWNER_KEYPAIR], capture_output=True).stdout.strip()
-PYTH_OWNER_ADDRESS = sol_run_or_die(
-    "address", ["--keypair", PYTH_PROGRAM_KEYPAIR], capture_output=True).stdout.strip()
-
-
-# Top up pyth2wormhole owner
-sol_run_or_die("airdrop", [
-    str(SOL_AIRDROP_AMT),
-    "--keypair",  P2W_OWNER_KEYPAIR,
-    "--commitment", "finalized",
-], capture_output=True)
-
-# Initialize pyth2wormhole
-init_result = run_or_die([
-    "pyth2wormhole-client",
-    "--log-level", "4",
-    "--p2w-addr", P2W_ADDRESS,
-    "--rpc-url", SOL_RPC_URL,
-    "--payer", P2W_OWNER_KEYPAIR,
-    "init",
-    "--wh-prog", WORMHOLE_ADDRESS,
-    "--owner", P2W_OWNER_ADDRESS,
-    "--pyth-owner", PYTH_OWNER_ADDRESS,
-], capture_output=True, die=False)
-
-if init_result.returncode != 0:
-    print("NOTE: pyth2wormhole-client init failed, retrying with set_config")
-    run_or_die([
+    for acc in pyth_accounts:
+
+        name = acc["name"]
+        price = acc["price"]
+        product = acc["product"]
+
+        cfg_yaml += f"""
+    - name: {name}
+      price_addr: {price}
+      product_addr: {product}"""
+
+    with open(P2W_ATTESTATION_CFG, "w") as f:
+        f.write(cfg_yaml)
+        f.flush()
+        
+
+attest_result = run_or_die(
+    [
         "pyth2wormhole-client",
-        "--log-level", "4",
-        "--p2w-addr", P2W_ADDRESS,
-        "--rpc-url", SOL_RPC_URL,
-        "--payer", P2W_OWNER_KEYPAIR,
-        "set-config",
-        "--owner", P2W_OWNER_KEYPAIR,
-        "--new-owner", P2W_OWNER_ADDRESS,
-        "--new-wh-prog", WORMHOLE_ADDRESS,
-        "--new-pyth-owner", PYTH_OWNER_ADDRESS,
-    ], capture_output=True)
-
-# Retrieve current price/product pubkeys from the pyth publisher
-conn = HTTPConnection(PYTH_ACCOUNTS_HOST, PYTH_ACCOUNTS_PORT)
-
-conn.request("GET", "/")
-
-res = conn.getresponse()
-
-pyth_accounts = None
-
-if res.getheader("Content-Type") == "application/json":
-    pyth_accounts = json.load(res)
-else:
-    print(f"Bad Content type {res.getheader('Content-Type')}", file=sys.stderr)
-    sys.exit(1)
-
-price_addr = pyth_accounts["price"]
-product_addr = pyth_accounts["product"]
-
-nonce = 0
-attest_result = run_or_die([
-    "pyth2wormhole-client",
-    "--log-level", "4",
-    "--p2w-addr", P2W_ADDRESS,
-    "--rpc-url", SOL_RPC_URL,
-    "--payer", P2W_OWNER_KEYPAIR,
-    "attest",
-    "--price", price_addr,
-    "--product", product_addr,
-    "--nonce", str(nonce),
-], capture_output=True)
-
-print("p2w_autoattest ready to roll.")
-print(f"ACCOUNTS: {pyth_accounts}")
-print(f"Attest Interval: {P2W_ATTEST_INTERVAL}")
+        "--log-level",
+        "4",
+        "--p2w-addr",
+        P2W_SOL_ADDRESS,
+        "--rpc-url",
+        SOL_RPC_URL,
+        "--payer",
+        P2W_OWNER_KEYPAIR,
+        "attest",
+        "-f",
+        P2W_ATTESTATION_CFG
+        
+    ],
+    capture_output=True,
+)
+
+logging.info("p2w_autoattest ready to roll!")
+logging.info(f"Attest Interval: {P2W_ATTEST_INTERVAL}")
 
 # Serve p2w endpoint
 endpoint_thread = threading.Thread(target=serve_attestations, daemon=True)
@@ -143,34 +208,32 @@ endpoint_thread.start()
 readiness_thread = threading.Thread(target=readiness, daemon=True)
 readiness_thread.start()
 
-seqno_regex = re.compile(r"^Sequence number: (\d+)")
+seqno_regex = re.compile(r"Sequence number: (\d+)")
 
-nonce = 1
 while True:
-    attest_result = run_or_die([
-        "pyth2wormhole-client",
-        "--log-level", "4",
-        "--p2w-addr", P2W_ADDRESS,
-        "--rpc-url", SOL_RPC_URL,
-        "--payer", P2W_OWNER_KEYPAIR,
-        "attest",
-        "--price", price_addr,
-        "--product", product_addr,
-        "--nonce", str(nonce),
-    ], capture_output=True)
+    matches = seqno_regex.findall(attest_result.stdout)
+
+    seqnos = list(map(lambda m: int(m), matches))
+
+    ATTESTATIONS["pendingSeqnos"] += seqnos
+
+    logging.info(f"{len(seqnos)} batch seqno(s) received: {seqnos})")
+
+    attest_result = run_or_die(
+        [
+            "pyth2wormhole-client",
+            "--log-level",
+            "4",
+            "--p2w-addr",
+            P2W_SOL_ADDRESS,
+            "--rpc-url",
+            SOL_RPC_URL,
+            "--payer",
+            P2W_OWNER_KEYPAIR,
+            "attest",
+            "-f",
+            P2W_ATTESTATION_CFG
+        ],
+        capture_output=True,
+    )
     time.sleep(P2W_ATTEST_INTERVAL)
-
-    matches = seqno_regex.match(attest_result.stdout)
-
-    if matches is not None:
-        seqno = int(matches.group(1))
-        print(f"Got seqno {seqno}")
-
-        ATTESTATIONS["pendingSeqnos"].append(seqno)
-
-    else:
-        print(f"Warning: Could not get sequence number")
-
-    nonce += 1
-
-readiness_thread.join()

+ 46 - 30
third_party/pyth/pyth_publisher.py

@@ -5,11 +5,13 @@ from pyth_utils import *
 from http.server import HTTPServer, BaseHTTPRequestHandler
 
 import json
+import os
 import random
 import sys
 import threading
 import time
 
+PYTH_TEST_SYMBOL_COUNT = int(os.environ.get("PYTH_TEST_SYMBOL_COUNT", "9"))
 
 class PythAccEndpoint(BaseHTTPRequestHandler):
     """
@@ -19,7 +21,7 @@ class PythAccEndpoint(BaseHTTPRequestHandler):
     def do_GET(self):
         print(f"Got path {self.path}")
         sys.stdout.flush()
-        data = json.dumps(ACCOUNTS).encode("utf-8")
+        data = json.dumps(TEST_SYMBOLS).encode("utf-8")
         print(f"Sending:\n{data}")
 
         self.send_response(200)
@@ -30,7 +32,7 @@ class PythAccEndpoint(BaseHTTPRequestHandler):
         self.wfile.flush()
 
 
-ACCOUNTS = dict()
+TEST_SYMBOLS = []
 
 
 def publisher_random_update(price_pubkey):
@@ -65,35 +67,50 @@ sol_run_or_die("airdrop", [
 # Create a mapping
 pyth_run_or_die("init_mapping")
 
-# Add a product
-prod_pubkey = pyth_run_or_die(
-    "add_product", capture_output=True).stdout.strip()
-print(f"Added product {prod_pubkey}")
-
-# Add a price
-price_pubkey = pyth_run_or_die(
-    "add_price",
-    args=[prod_pubkey, "price"],
-    confirm=False,
-    capture_output=True
-).stdout.strip()
-
-print(f"Added price {price_pubkey}")
+print(f"Creating {PYTH_TEST_SYMBOL_COUNT} test Pyth symbols")
 
 publisher_pubkey = sol_run_or_die("address", args=[
     "--keypair", PYTH_PUBLISHER_KEYPAIR
 ], capture_output=True).stdout.strip()
 
-# Become a publisher
-pyth_run_or_die(
-    "add_publisher", args=[publisher_pubkey, price_pubkey],
-    confirm=False,
-    debug=True,
-    capture_output=True)
-print(f"Added publisher {publisher_pubkey}")
+for i in range(PYTH_TEST_SYMBOL_COUNT):
+    symbol_name = f"Test symbol {i}"
+    # Add a product
+    prod_pubkey = pyth_run_or_die(
+        "add_product", capture_output=True).stdout.strip()
+
+    print(f"{symbol_name}: Added product {prod_pubkey}")
+
+    # Add a price
+    price_pubkey = pyth_run_or_die(
+        "add_price",
+        args=[prod_pubkey, "price"],
+        confirm=False,
+        capture_output=True
+    ).stdout.strip()
+
+    print(f"{symbol_name}: Added price {price_pubkey}")
+
+    # Become a publisher for the new price
+    pyth_run_or_die(
+        "add_publisher", args=[publisher_pubkey, price_pubkey],
+        confirm=False,
+        debug=True,
+        capture_output=True)
+    print(f"{symbol_name}: Added publisher {publisher_pubkey}")
+
+    # Update the prices as the newly added publisher 
+    publisher_random_update(price_pubkey)
 
-# Update the price as the newly added publisher
-publisher_random_update(price_pubkey)
+    sym = {
+        "name": symbol_name,
+        "product": prod_pubkey,
+        "price": price_pubkey
+    }
+
+    TEST_SYMBOLS.append(sym)
+
+    sys.stdout.flush()
 
 print(
     f"Mock updates ready to roll. Updating every {str(PYTH_PUBLISHER_INTERVAL)} seconds")
@@ -101,17 +118,16 @@ print(
 # Spin off the readiness probe endpoint into a separate thread
 readiness_thread = threading.Thread(target=readiness, daemon=True)
 
-# Start an HTTP endpoint for looking up product/price address
+# Start an HTTP endpoint for looking up test product/price addresses
 http_service = threading.Thread(target=accounts_endpoint, daemon=True)
 
-ACCOUNTS["product"] = prod_pubkey
-ACCOUNTS["price"] = price_pubkey
-
 readiness_thread.start()
 http_service.start()
 
 while True:
-    publisher_random_update(price_pubkey)
+    for sym in TEST_SYMBOLS:
+        publisher_random_update(sym["price"])
+
     time.sleep(PYTH_PUBLISHER_INTERVAL)
     sys.stdout.flush()
 

+ 27 - 19
third_party/pyth/pyth_utils.py

@@ -1,22 +1,31 @@
 import os
 import socketserver
-import sys
 import subprocess
+import sys
 
+# Settings specific to local devnet Pyth instance
 PYTH = os.environ.get("PYTH", "./pyth")
 PYTH_KEY_STORE = os.environ.get("PYTH_KEY_STORE", "/home/pyth/.pythd")
 PYTH_PROGRAM_KEYPAIR = os.environ.get(
-    "PYTH_PROGRAM_KEYPAIR", f"{PYTH_KEY_STORE}/publish_key_pair.json")
+    "PYTH_PROGRAM_KEYPAIR", f"{PYTH_KEY_STORE}/publish_key_pair.json"
+)
 PYTH_PROGRAM_SO_PATH = os.environ.get("PYTH_PROGRAM_SO", "../target/oracle.so")
 PYTH_PUBLISHER_KEYPAIR = os.environ.get(
-    "PYTH_PUBLISHER_KEYPAIR", f"{PYTH_KEY_STORE}/publish_key_pair.json")
+    "PYTH_PUBLISHER_KEYPAIR", f"{PYTH_KEY_STORE}/publish_key_pair.json"
+)
 PYTH_PUBLISHER_INTERVAL = float(os.environ.get("PYTH_PUBLISHER_INTERVAL", "5"))
 
-SOL_AIRDROP_AMT = 100
-SOL_RPC_HOST = "solana-devnet"
-SOL_RPC_PORT = 8899
-SOL_RPC_URL = f"http://{SOL_RPC_HOST}:{str(SOL_RPC_PORT)}"
+# 0 setting disables airdropping
+SOL_AIRDROP_AMT = int(os.environ.get("SOL_AIRDROP_AMT", 0))
+
+# SOL RPC settings
+SOL_RPC_HOST = os.environ.get("SOL_RPC_HOST", "solana-devnet")
+SOL_RPC_PORT = int(os.environ.get("SOL_RPC_PORT", 8899))
+SOL_RPC_URL = os.environ.get(
+    "SOL_RPC_URL", "http://{0}:{1}".format(SOL_RPC_HOST, SOL_RPC_PORT)
+)
 
+# A TCP port we open when a service is ready
 READINESS_PORT = int(os.environ.get("READINESS_PORT", "2000"))
 
 
@@ -24,12 +33,12 @@ def run_or_die(args, die=True, **kwargs):
     """
     Opinionated subprocess.run() call with fancy logging
     """
-    args_readable = ' '.join(args)
+    args_readable = " ".join(args)
     print(f"CMD RUN\t{args_readable}", file=sys.stderr)
     sys.stderr.flush()
     ret = subprocess.run(args, text=True, **kwargs)
 
-    if ret.returncode is not 0:
+    if ret.returncode != 0:
         print(f"CMD FAIL {ret.returncode}\t{args_readable}", file=sys.stderr)
 
         out = ret.stdout if ret.stdout is not None else "<not captured>"
@@ -41,7 +50,7 @@ def run_or_die(args, die=True, **kwargs):
         if die:
             sys.exit(ret.returncode)
         else:
-            print(f"CMD DIE FALSE", file=sys.stderr)
+            print(f'{"CMD DIE FALSE"}', file=sys.stderr)
 
     else:
         print(f"CMD OK\t{args_readable}", file=sys.stderr)
@@ -54,23 +63,21 @@ def pyth_run_or_die(subcommand, args=[], debug=False, confirm=True, **kwargs):
     Pyth boilerplate in front of run_or_die
     """
     return run_or_die(
-        [PYTH, subcommand]
-        + args
-        + (["-d"] if debug else [])
+        [PYTH, subcommand] + args + (["-d"] if debug else [])
         # Note: not all pyth subcommands accept -n
         + ([] if confirm else ["-n"])
         + ["-k", PYTH_KEY_STORE]
         + ["-r", SOL_RPC_HOST]
-        + ["-c", "finalized"], **kwargs)
+        + ["-c", "finalized"],
+        **kwargs,
+    )
 
 
 def sol_run_or_die(subcommand, args=[], **kwargs):
     """
     Solana boilerplate in front of run_or_die
     """
-    return run_or_die(["solana", subcommand]
-                      + args
-                      + ["--url", SOL_RPC_URL], **kwargs)
+    return run_or_die(["solana", subcommand] + args + ["--url", SOL_RPC_URL], **kwargs)
 
 
 class ReadinessTCPHandler(socketserver.StreamRequestHandler):
@@ -83,6 +90,7 @@ def readiness():
     """
     Accept connections from readiness probe
     """
-    with socketserver.TCPServer(("0.0.0.0", READINESS_PORT), ReadinessTCPHandler) as srv:
+    with socketserver.TCPServer(
+        ("0.0.0.0", READINESS_PORT), ReadinessTCPHandler
+    ) as srv:
         srv.serve_forever()
-    # run_or_die(["nc", "-k", "-l", "-p", READINESS_PORT])

Some files were not shown because too many files changed in this diff