Explorar o código

Merge commit '496e2f8880f4301540500ce19f0808f24756fdc1' as 'target_chains/sui/vendor/wormhole_movement_testnet'

Ali Behjati hai 1 ano
pai
achega
61d7019894
Modificáronse 100 ficheiros con 20717 adicións e 0 borrados
  1. 2 0
      target_chains/sui/vendor/wormhole_movement_testnet/.gitignore
  2. 13 0
      target_chains/sui/vendor/wormhole_movement_testnet/Docker.md
  3. 33 0
      target_chains/sui/vendor/wormhole_movement_testnet/Dockerfile
  4. 24 0
      target_chains/sui/vendor/wormhole_movement_testnet/Dockerfile.base
  5. 15 0
      target_chains/sui/vendor/wormhole_movement_testnet/Makefile
  6. 114 0
      target_chains/sui/vendor/wormhole_movement_testnet/NOTES.md
  7. 130 0
      target_chains/sui/vendor/wormhole_movement_testnet/README.md
  8. 125 0
      target_chains/sui/vendor/wormhole_movement_testnet/devnet/127.0.0.1-36219.yaml
  9. 125 0
      target_chains/sui/vendor/wormhole_movement_testnet/devnet/127.0.0.1-36853.yaml
  10. 125 0
      target_chains/sui/vendor/wormhole_movement_testnet/devnet/127.0.0.1-39101.yaml
  11. 125 0
      target_chains/sui/vendor/wormhole_movement_testnet/devnet/127.0.0.1-39187.yaml
  12. 12 0
      target_chains/sui/vendor/wormhole_movement_testnet/devnet/client.yaml
  13. 34 0
      target_chains/sui/vendor/wormhole_movement_testnet/devnet/fullnode.yaml
  14. BIN=BIN
      target_chains/sui/vendor/wormhole_movement_testnet/devnet/genesis.blob
  15. 55 0
      target_chains/sui/vendor/wormhole_movement_testnet/devnet/genesis_config
  16. 499 0
      target_chains/sui/vendor/wormhole_movement_testnet/devnet/network.yaml
  17. 1 0
      target_chains/sui/vendor/wormhole_movement_testnet/examples/coins/.gitignore
  18. 15 0
      target_chains/sui/vendor/wormhole_movement_testnet/examples/coins/Makefile
  19. 17 0
      target_chains/sui/vendor/wormhole_movement_testnet/examples/coins/Move.devnet.toml
  20. 52 0
      target_chains/sui/vendor/wormhole_movement_testnet/examples/coins/Move.lock
  21. 28 0
      target_chains/sui/vendor/wormhole_movement_testnet/examples/coins/Move.toml
  22. 210 0
      target_chains/sui/vendor/wormhole_movement_testnet/examples/coins/sources/coin.move
  23. 72 0
      target_chains/sui/vendor/wormhole_movement_testnet/examples/coins/sources/coin_10.move
  24. 72 0
      target_chains/sui/vendor/wormhole_movement_testnet/examples/coins/sources/coin_8.move
  25. 20 0
      target_chains/sui/vendor/wormhole_movement_testnet/examples/core_messages/Makefile
  26. 14 0
      target_chains/sui/vendor/wormhole_movement_testnet/examples/core_messages/Move.devnet.toml
  27. 39 0
      target_chains/sui/vendor/wormhole_movement_testnet/examples/core_messages/Move.lock
  28. 21 0
      target_chains/sui/vendor/wormhole_movement_testnet/examples/core_messages/Move.toml
  29. 149 0
      target_chains/sui/vendor/wormhole_movement_testnet/examples/core_messages/sources/sender.move
  30. 3 0
      target_chains/sui/vendor/wormhole_movement_testnet/examples/templates/README.md
  31. 19 0
      target_chains/sui/vendor/wormhole_movement_testnet/examples/templates/wrapped_coin/Move.toml
  32. 21 0
      target_chains/sui/vendor/wormhole_movement_testnet/examples/templates/wrapped_coin/sources/coin.move
  33. 119 0
      target_chains/sui/vendor/wormhole_movement_testnet/scripts/deploy.sh
  34. 11 0
      target_chains/sui/vendor/wormhole_movement_testnet/scripts/node_builder.sh
  35. 22 0
      target_chains/sui/vendor/wormhole_movement_testnet/scripts/register_devnet.sh
  36. 3 0
      target_chains/sui/vendor/wormhole_movement_testnet/scripts/setup_rust.sh
  37. 5 0
      target_chains/sui/vendor/wormhole_movement_testnet/scripts/start_node.sh
  38. 31 0
      target_chains/sui/vendor/wormhole_movement_testnet/scripts/switch.sh
  39. 6 0
      target_chains/sui/vendor/wormhole_movement_testnet/scripts/wait_for_devnet.sh
  40. 4 0
      target_chains/sui/vendor/wormhole_movement_testnet/testing/.gitignore
  41. 13 0
      target_chains/sui/vendor/wormhole_movement_testnet/testing/Makefile
  42. 78 0
      target_chains/sui/vendor/wormhole_movement_testnet/testing/js/00_environment.ts
  43. 109 0
      target_chains/sui/vendor/wormhole_movement_testnet/testing/js/01_wormhole.ts
  44. 32 0
      target_chains/sui/vendor/wormhole_movement_testnet/testing/js/helpers/build.ts
  45. 40 0
      target_chains/sui/vendor/wormhole_movement_testnet/testing/js/helpers/consts.ts
  46. 42 0
      target_chains/sui/vendor/wormhole_movement_testnet/testing/js/helpers/error/moveAbort.ts
  47. 22 0
      target_chains/sui/vendor/wormhole_movement_testnet/testing/js/helpers/error/wormhole.ts
  48. 75 0
      target_chains/sui/vendor/wormhole_movement_testnet/testing/js/helpers/setup.ts
  49. 73 0
      target_chains/sui/vendor/wormhole_movement_testnet/testing/js/helpers/upgrade.ts
  50. 27 0
      target_chains/sui/vendor/wormhole_movement_testnet/testing/js/helpers/utils.ts
  51. 31 0
      target_chains/sui/vendor/wormhole_movement_testnet/testing/js/helpers/wormhole/testPublishMessage.ts
  52. 5917 0
      target_chains/sui/vendor/wormhole_movement_testnet/testing/package-lock.json
  53. 22 0
      target_chains/sui/vendor/wormhole_movement_testnet/testing/package.json
  54. 35 0
      target_chains/sui/vendor/wormhole_movement_testnet/testing/run_integration_test.sh
  55. 300 0
      target_chains/sui/vendor/wormhole_movement_testnet/testing/scripts/upgrade-token-bridge.ts
  56. 267 0
      target_chains/sui/vendor/wormhole_movement_testnet/testing/scripts/upgrade-wormhole.ts
  57. 12 0
      target_chains/sui/vendor/wormhole_movement_testnet/testing/sui_config/client.yaml
  58. 53 0
      target_chains/sui/vendor/wormhole_movement_testnet/testing/sui_config/fullnode.yaml
  59. BIN=BIN
      target_chains/sui/vendor/wormhole_movement_testnet/testing/sui_config/genesis.blob
  60. 323 0
      target_chains/sui/vendor/wormhole_movement_testnet/testing/sui_config/network.yaml
  61. 7 0
      target_chains/sui/vendor/wormhole_movement_testnet/testing/sui_config/sui.keystore
  62. 81 0
      target_chains/sui/vendor/wormhole_movement_testnet/testing/sui_config/validator-config-0.yaml
  63. 81 0
      target_chains/sui/vendor/wormhole_movement_testnet/testing/sui_config/validator-config-1.yaml
  64. 81 0
      target_chains/sui/vendor/wormhole_movement_testnet/testing/sui_config/validator-config-2.yaml
  65. 81 0
      target_chains/sui/vendor/wormhole_movement_testnet/testing/sui_config/validator-config-3.yaml
  66. 12 0
      target_chains/sui/vendor/wormhole_movement_testnet/testing/tsconfig.json
  67. 1 0
      target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/.gitignore
  68. 18 0
      target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/Makefile
  69. 14 0
      target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/Move.devnet.toml
  70. 39 0
      target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/Move.lock
  71. 15 0
      target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/Move.mainnet.toml
  72. 15 0
      target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/Move.testnet.toml
  73. 21 0
      target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/Move.toml
  74. 385 0
      target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/attest_token.move
  75. 1228 0
      target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/complete_transfer.move
  76. 776 0
      target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/complete_transfer_with_payload.move
  77. 643 0
      target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/create_wrapped.move
  78. 167 0
      target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/datatypes/normalized_amount.move
  79. 297 0
      target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/governance/register_chain.move
  80. 125 0
      target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/governance/upgrade_contract.move
  81. 251 0
      target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/messages/asset_meta.move
  82. 311 0
      target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/messages/transfer.move
  83. 352 0
      target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/messages/transfer_with_payload.move
  84. 216 0
      target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/migrate.move
  85. 220 0
      target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/resources/native_asset.move
  86. 784 0
      target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/resources/token_registry.move
  87. 806 0
      target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/resources/wrapped_asset.move
  88. 78 0
      target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/setup.move
  89. 396 0
      target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/state.move
  90. 165 0
      target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/test/coin_native_10.move
  91. 165 0
      target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/test/coin_native_4.move
  92. 193 0
      target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/test/coin_wrapped_12.move
  93. 194 0
      target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/test/coin_wrapped_7.move
  94. 146 0
      target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/test/dummy_message.move
  95. 136 0
      target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/test/token_bridge_scenario.move
  96. 1053 0
      target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/transfer_tokens.move
  97. 812 0
      target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/transfer_tokens_with_payload.move
  98. 48 0
      target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/utils/coin_utils.move
  99. 97 0
      target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/utils/string_utils.move
  100. 351 0
      target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/vaa.move

+ 2 - 0
target_chains/sui/vendor/wormhole_movement_testnet/.gitignore

@@ -0,0 +1,2 @@
+deploy.out
+sui.log.*

+ 13 - 0
target_chains/sui/vendor/wormhole_movement_testnet/Docker.md

@@ -0,0 +1,13 @@
+# first build the image
+
+cd ..; DOCKER_BUILDKIT=1 docker build --no-cache --progress plain -f sui/Dockerfile.base -t sui .
+
+# tag the image with the appropriate version
+
+docker tag sui:latest ghcr.io/wormhole-foundation/sui:1.19.1-mainnet
+
+# push to ghcr
+
+docker push ghcr.io/wormhole-foundation/sui:1.19.1-mainnet
+
+echo remember to update both Dockerfile and Dockerfile.export

+ 33 - 0
target_chains/sui/vendor/wormhole_movement_testnet/Dockerfile

@@ -0,0 +1,33 @@
+FROM cli-gen AS cli-export
+FROM const-gen AS const-export
+FROM ghcr.io/wormhole-foundation/sui:1.19.1-mainnet@sha256:544a1b2aa5701fae25a19aed3c5e8c24e0caf7d1c9f511b6844d339a8f0b2a00 as sui
+
+# initial run
+# COPY sui/devnet/genesis_config genesis_config
+# RUN sui genesis -f --from-config genesis_config
+
+# subsequent runs after committing files from /root/.sui/sui_config/
+COPY sui/devnet/ /root/.sui/sui_config/
+
+WORKDIR /tmp
+
+COPY sui/scripts/ scripts
+COPY sui/wormhole/ wormhole
+COPY sui/token_bridge/ token_bridge
+COPY sui/examples/ examples
+COPY sui/Makefile Makefile
+
+# Copy .env and CLI
+COPY --from=const-export .env .env
+COPY --from=cli-export clients/js /cli
+
+# Link `worm`
+WORKDIR /cli
+
+RUN npm link
+
+FROM sui AS tests
+
+WORKDIR /tmp
+
+RUN --mount=type=cache,target=/root/.move,id=move_cache make test

+ 24 - 0
target_chains/sui/vendor/wormhole_movement_testnet/Dockerfile.base

@@ -0,0 +1,24 @@
+FROM rust:1.62@sha256:5777f201f507075309c4d2d1c1e8d8219e654ae1de154c844341050016a64a0c as sui-node
+
+WORKDIR /tmp
+
+RUN curl -L https://github.com/MystenLabs/sui/releases/download/mainnet-v1.19.1/sui-mainnet-v1.19.1-ubuntu-x86_64.tgz > sui-mainnet-v1.19.1-ubuntu-x86_64.tgz
+RUN echo "6a8cc96759760293143a00fe7031a5fea70d2dff5b98d18c0470c09555da63e0  sui-mainnet-v1.19.1-ubuntu-x86_64.tgz" | sha256sum -c --status
+
+RUN tar -xvf sui-mainnet-v1.19.1-ubuntu-x86_64.tgz
+RUN mv target/release/sui-ubuntu-x86_64 /bin/sui
+RUN mv target/release/sui-faucet-ubuntu-x86_64 /bin/sui-faucet
+RUN mv target/release/sui-node-ubuntu-x86_64 /bin/sui-node
+
+RUN rm sui-mainnet-v1.19.1-ubuntu-x86_64.tgz
+
+RUN apt-get update
+RUN apt-get install -y ca-certificates curl gnupg
+RUN mkdir -p /etc/apt/keyrings
+RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
+
+ARG NODE_MAJOR=18
+RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
+
+RUN apt-get update
+RUN apt-get install nodejs -y

+ 15 - 0
target_chains/sui/vendor/wormhole_movement_testnet/Makefile

@@ -0,0 +1,15 @@
+TEST_CONTRACT_DIRS := wormhole token_bridge examples/coins examples/core_messages
+CLEAN_CONTRACT_DIRS := wormhole token_bridge examples/coins examples/core_messages
+
+.PHONY: clean
+clean:
+	$(foreach dir,$(TEST_CONTRACT_DIRS), make -C $(dir) $@ &&) true
+
+.PHONY: test
+test:
+	$(foreach dir,$(TEST_CONTRACT_DIRS), make -C $(dir) $@ &&) true
+
+test-docker:
+	DOCKER_BUILDKIT=1 docker build --progress plain  -f ../Dockerfile.cli -t cli-gen ..
+	DOCKER_BUILDKIT=1 docker build --build-arg num_guardians=1 --progress plain -f ../Dockerfile.const -t const-gen ..
+	DOCKER_BUILDKIT=1 docker build -f Dockerfile ..

+ 114 - 0
target_chains/sui/vendor/wormhole_movement_testnet/NOTES.md

@@ -0,0 +1,114 @@
+brew install cmake
+
+ rustup install stable-x86_64-apple-darwin
+ #rustup target add stable-x86_64-apple-darwin
+ rustup target add x86_64-apple-darwin
+
+=== Building
+
+  % ./node_builder.sh
+
+=== Running
+
+  % ./start_node.sh
+
+# If you don't remember your newly generated address
+
+   % sui client addresses
+   Showing 1 results.
+   0x13b3cb89cf3226d3b860294fc75dc6c91f0c5ecf
+
+# Give yourself some money
+
+   % scripts/faucet.sh `sui client addresses | tail -1`
+
+# Looking at the prefunded address
+
+   % sui client objects --address 0x13b3cb89cf3226d3b860294fc75dc6c91f0c5ecf
+
+=== Boot tilt
+
+# fund our standard account
+
+ We don't run a faucet since it doesn't always unlock the client LOCK files.  So, instead we just steal a chunk of coins
+ from the default accounts created when the node was initialized.  Once sui is showing as live...
+
+``` sh
+ % kubectl exec -it sui-0 -c sui-node -- /tmp/funder.sh
+```
+
+# getting into the sui k8s node (if you need to crawl around)
+
+   kubectl exec -it sui-0 -c sui-node -- /bin/bash
+   kubectl exec -it guardian-0 -c guardiand -- /bin/bash
+
+# setup the client.yaml
+
+``` sh
+  % rm -rf $HOME/.sui
+  % sui keytool import "daughter exclude wheat pudding police weapon giggle taste space whip satoshi occur" ed25519
+  % sui client
+```
+     point it at http://localhost:9000.  The key you create doesn't matter.
+
+# edit $HOME/.sui/sui_config/client.yaml
+
+``` sh
+   sed -i -e 's/active_address.*/active_address: "0x13b3cb89cf3226d3b860294fc75dc6c91f0c5ecf"/' ~/.sui/sui_config/client.yaml 
+```
+
+
+# deploy the contract
+
+``` sh
+  % scripts/deploy.sh
+```
+
+# start the watcher
+
+``` sh
+  % . env.sh
+  % python3 tests/ws.py
+```
+
+# publish a message (different window)
+
+``` sh
+  % . env.sh
+  % scripts/publish_message.sh
+```
+
+==
+
+docker run -it -v `pwd`:`pwd` -w `pwd` --net=host ghcr.io/wormhole-foundation/sui:0.16.0 bash
+dnf -y install git make
+
+``` sh
+  % rm -rf $HOME/.sui
+  % sui keytool import "daughter exclude wheat pudding police weapon giggle taste space whip satoshi occur" secp256k1
+  % sui client
+```
+
+to get a new emitter
+
+  kubectl exec -it sui-0 -c sui-node -- /tmp/funder.sh
+  scripts/deploy.sh
+  . env.sh
+  sui client call --function get_new_emitter --module wormhole --package $WORM_PACKAGE --gas-budget 20000 --args \"$WORM_STATE\" 
+
+  sui client objects
+  scripts/publish_message.sh 0x165ef7366c4267c6506bcf63d2419556f34f48d6
+
+
+curl -s -X POST -d '{"jsonrpc":"2.0", "id": 1, "method": "sui_getEvents", "params": [{"MoveEvent": "0xf4179152ab02e4212d7e7b20f37a9a86ab6d50fb::state::WormholeMessage"}, null, 10, true]}' -H 'Content-Type: application/json' http://127.0.0.1:9002 | jq
+
+curl -s -X POST -d '{"jsonrpc":"2.0", "id": 1, "method": "sui_getEvents", "params": [{"Transaction": "cL+uWFEVcQrkAiOxOJmaK7JmlOJdE3/8X5JFbJwBxCQ="}, null, 10, true]}' -H 'Content-Type: application/json' http://127.0.0.1:9002 | jq
+
+"txhash": "0x70bfae585115710ae40223b138999a2bb26694e25d137ffc5f92456c9c01c424", "txhash_b58": "8b8Bn8MUqAWeVz2BE5hMicC9KaRkV6UM4v1JLWGUjxcT", "
+Digest: cL+uWFEVcQrkAiOxOJmaK7JmlOJdE3/8X5JFbJwBxCQ=
+
+  kubectl exec -it guardian-0 -- /guardiand admin send-observation-request --socket /tmp/admin.sock 21 70bfae585115710ae40223b138999a2bb26694e25d137ffc5f92456c9c01c424
+
+// curl -s -X POST -d '{"jsonrpc":"2.0", "id": 1, "method": "sui_getCommitteeInfo", "params": []}' -H 'Content-Type: application/json' http://127.0.0.1:9002 | jq
+
+// curl -s -X POST -d '{"jsonrpc":"2.0", "id": 1, "method": "sui_getLatestCheckpointSequenceNumber", "params": []}' -H 'Content-Type: application/json' http://127.0.0.1:9000 

+ 130 - 0
target_chains/sui/vendor/wormhole_movement_testnet/README.md

@@ -0,0 +1,130 @@
+# Wormhole on Sui
+
+This folder contains the reference implementation of the Wormhole cross-chain
+messaging protocol smart contracts on the [Sui](https://mystenlabs.com/)
+blockchain, implemented in the [Move](https://move-book.com/) programming
+language.
+
+# Project structure
+
+The project is laid out as follows:
+
+- [wormhole](./wormhole) the core messaging layer
+- [token_bridge](./token_bridge) the asset transfer layer
+- [coin](./coin) a template for creating Wormhole wrapped coins
+
+# Installation
+
+Make sure your Cargo version is at least 1.65.0 and then follow the steps below:
+
+- https://docs.sui.io/build/install
+
+#https://docs.sui.io/guides/developer/getting-started/sui-install# Prerequisites
+
+Install the `Sui` CLI. This tool is used to compile the contracts and run the tests.
+
+```sh
+cargo install --locked --git https://github.com/MystenLabs/sui.git --rev 041c5f2bae2fe52079e44b70514333532d69f4e6 sui
+```
+
+Some useful Sui CLI commands are
+
+- `sui start` to spin up a local network
+- `rpc-server` to start a server for handling rpc calls
+
+Next, install the [worm](../clients/js/README.md) CLI tool by running
+
+```sh
+wormhole/clients/js $ make install
+```
+
+`worm` is the swiss army knife for interacting with wormhole contracts on all
+supported chains, and generating signed messages (VAAs) for testing.
+
+As an optional, but recommended step, install the
+[move-analyzer](https://github.com/move-language/move/tree/main/language/move-analyzer)
+Language Server (LSP):
+
+```sh
+cargo install --git https://github.com/move-language/move.git move-analyzer --branch main --features "address32"
+```
+
+This installs the LSP backend which is then supported by most popular editors such as [emacs](https://github.com/emacs-lsp/lsp-mode), [vim](https://github.com/neoclide/coc.nvim), and even [vscode](https://marketplace.visualstudio.com/items?itemName=move.move-analyzer).
+
+<details>
+    <summary>For emacs, you may need to add the following to your config file:</summary>
+
+```lisp
+;; Move
+(define-derived-mode move-mode rust-mode "Move"
+  :group 'move-mode)
+
+(add-to-list 'auto-mode-alist '("\\.move\\'" . move-mode))
+
+(with-eval-after-load 'lsp-mode
+  (add-to-list 'lsp-language-id-configuration
+    '(move-mode . "move"))
+
+  (lsp-register-client
+    (make-lsp-client :new-connection (lsp-stdio-connection "move-analyzer")
+                     :activation-fn (lsp-activate-on "move")
+                     :server-id 'move-analyzer)))
+```
+
+</details>
+
+## Building & running tests
+
+The project uses a simple `make`-based build system for building and running
+tests. Running `make test` in this directory will run the tests for each
+contract. If you only want to run the tests for, say, the token bridge contract,
+then you can run `make test` in the `token_bridge` directory, or run `make -C
+token_bridge test` from this directory.
+
+Additionally, `make test-docker` runs the tests in a docker container which is
+set up with all the necessary dependencies. This is the command that runs in CI.
+
+## Running a local validator and deploying the contracts to it
+
+Simply run
+
+```sh
+worm start-validator sui
+```
+
+which will start a local sui validator with an RPC endpoint at `0.0.0.0:9000`.
+
+Once the validator is running, the contracts are ready to deploy. In the
+[scripts](./scripts) directory, run
+
+```sh
+scripts $ ./deploy.sh devnet
+```
+
+This will deploy the core contract and the token bridge.
+
+When you make a change to the contract, you can simply restart the validator and
+run the deploy script again.
+
+<!-- However, a better way is to run one of the following scripts:
+
+``` sh
+scripts $ ./upgrade devnet Core # for upgrading the wormhole contract
+scripts $ ./upgrade devnet TokenBridge # for upgrading the token bridge contract
+scripts $ ./upgrade devnet NFTBridge # for upgrading the NFT bridge contract
+```
+
+Behind the scenes, these scripts exercise the whole contract upgrade code path
+(see below), including generating and verifying a signed governance action, and
+the Move bytecode verifier checking ABI compatibility. If an upgrade here fails
+due to incompatibility, it will likely on mainnet too. (TODO: add CI action to
+simulate upgrades against main when there's a stable version) -->
+
+# Implementation notes / coding practices
+
+In this section, we describe some of the implementation design decisions and
+coding practices we converged on along the way. Note that the coding guidelines
+are prescriptive rather than descriptive, and the goal is for the contracts to
+ultimately follow these, but they might not during earlier development phases.
+
+### TODO

+ 125 - 0
target_chains/sui/vendor/wormhole_movement_testnet/devnet/127.0.0.1-36219.yaml

@@ -0,0 +1,125 @@
+---
+protocol-key-pair:
+  value: avYcyVgYMXTyaUYh9IRwLK0gSzl7YF6ZQDAbrS1Bhvo=
+worker-key-pair:
+  value: AAvfYqj1HPsXmthZ1t2Uw19vU6tdhK48YAFgkhJ7P/sV
+account-key-pair:
+  value: ABmHnCaxw0GWzW+1MZYfTDonS1wZsO8KO37SXgm6pqc6
+network-key-pair:
+  value: AEpJ6PVCvnrtaxREy8UNSiDwLPPrZMh12TbgELadmAHB
+db-path: /root/.sui/sui_config/authorities_db/8dcff6d15504
+network-address: /ip4/127.0.0.1/tcp/36219/http
+json-rpc-address: "127.0.0.1:37179"
+enable-experimental-rest-api: true
+metrics-address: "127.0.0.1:44423"
+admin-interface-port: 35585
+consensus-config:
+  address: /ip4/127.0.0.1/tcp/35107/http
+  db-path: /root/.sui/sui_config/consensus_db/8dcff6d15504
+  internal-worker-address: ~
+  max-pending-transactions: ~
+  max-submit-position: ~
+  submit-delay-step-override-millis: ~
+  narwhal-config:
+    header_num_of_batches_threshold: 32
+    max_header_num_of_batches: 1000
+    max_header_delay: 1000ms
+    min_header_delay: 500ms
+    gc_depth: 50
+    sync_retry_delay: 5000ms
+    sync_retry_nodes: 3
+    batch_size: 5000000
+    max_batch_delay: 100ms
+    max_concurrent_requests: 500000
+    prometheus_metrics:
+      socket_addr: /ip4/127.0.0.1/tcp/42177/http
+    network_admin_server:
+      primary_network_admin_server_port: 34745
+      worker_network_admin_server_base_port: 43111
+    anemo:
+      send_certificate_rate_limit: ~
+      report_batch_rate_limit: ~
+      request_batches_rate_limit: ~
+enable-event-processing: false
+enable-index-processing: true
+grpc-load-shed: ~
+grpc-concurrency-limit: 20000000000
+p2p-config:
+  listen-address: "127.0.0.1:41551"
+  external-address: /ip4/127.0.0.1/udp/41551
+  state-sync:
+    checkpoint-content-timeout-ms: 10000
+genesis:
+  genesis-file-location: /root/.sui/sui_config/genesis.blob
+authority-store-pruning-config:
+  num-latest-epoch-dbs-to-retain: 3
+  epoch-db-pruning-period-secs: 3600
+  num-epochs-to-retain: 0
+  max-checkpoints-in-batch: 10
+  max-transactions-in-batch: 1000
+end-of-epoch-broadcast-channel-capacity: 128
+checkpoint-executor-config:
+  checkpoint-execution-max-concurrency: 200
+  local-execution-timeout-sec: 30
+db-checkpoint-config:
+  perform-db-checkpoints-at-epoch-end: false
+indirect-objects-threshold: 18446744073709551615
+expensive-safety-check-config:
+  enable-epoch-sui-conservation-check: false
+  enable-deep-per-tx-sui-conservation-check: false
+  force-disable-epoch-sui-conservation-check: false
+  enable-state-consistency-check: false
+  force-disable-state-consistency-check: false
+  enable-secondary-index-checks: false
+transaction-deny-config:
+  package-publish-disabled: false
+  package-upgrade-disabled: false
+  shared-object-disabled: false
+  user-transaction-disabled: false
+  receiving-objects-disabled: false
+  zklogin-sig-disabled: false
+  zklogin-disabled-providers: []
+certificate-deny-config: {}
+state-debug-dump-config: {}
+state-archive-write-config:
+  concurrency: 0
+  use-for-pruning-watermark: false
+state-archive-read-config: []
+state-snapshot-write-config:
+  concurrency: 0
+indexer-max-subscriptions: ~
+transaction-kv-store-read-config:
+  base-url: ""
+jwk-fetch-interval-seconds: 3600
+zklogin-oauth-providers:
+  Mainnet:
+    - Facebook
+    - Google
+    - Twitch
+  Testnet:
+    - Facebook
+    - Google
+    - Twitch
+  Unknown:
+    - Apple
+    - Facebook
+    - Google
+    - Kakao
+    - Slack
+    - Twitch
+authority-overload-config:
+  max-txn-age-in-queue:
+    secs: 1
+    nanos: 0
+  overload-monitor-interval:
+    secs: 10
+    nanos: 0
+  execution-queue-latency-soft-limit:
+    secs: 1
+    nanos: 0
+  execution-queue-latency-hard-limit:
+    secs: 10
+    nanos: 0
+  max-load-shedding-percentage: 95
+  min-load-shedding-percentage-above-hard-limit: 50
+  safe-transaction-ready-rate: 100

+ 125 - 0
target_chains/sui/vendor/wormhole_movement_testnet/devnet/127.0.0.1-36853.yaml

@@ -0,0 +1,125 @@
+---
+protocol-key-pair:
+  value: OXnx3yM1C/ppgnDMx/o1d49fJs7E05kq11mXNae/O+I=
+worker-key-pair:
+  value: AE4ZKvLhbIyoYlv0y7q7aPHyU/Jty/D1AzILgYUs4VqC
+account-key-pair:
+  value: AEAh/lnBSwKKrazfLNz3J7DBu7W2EMuhcShk6MHJhxpT
+network-key-pair:
+  value: AHdOWNkwAgBFMTlwVSGkhI4COGDX40frs5xOz72DHvNm
+db-path: /root/.sui/sui_config/authorities_db/addeef94d898
+network-address: /ip4/127.0.0.1/tcp/36853/http
+json-rpc-address: "127.0.0.1:34043"
+enable-experimental-rest-api: true
+metrics-address: "127.0.0.1:45007"
+admin-interface-port: 36657
+consensus-config:
+  address: /ip4/127.0.0.1/tcp/45105/http
+  db-path: /root/.sui/sui_config/consensus_db/addeef94d898
+  internal-worker-address: ~
+  max-pending-transactions: ~
+  max-submit-position: ~
+  submit-delay-step-override-millis: ~
+  narwhal-config:
+    header_num_of_batches_threshold: 32
+    max_header_num_of_batches: 1000
+    max_header_delay: 1000ms
+    min_header_delay: 500ms
+    gc_depth: 50
+    sync_retry_delay: 5000ms
+    sync_retry_nodes: 3
+    batch_size: 5000000
+    max_batch_delay: 100ms
+    max_concurrent_requests: 500000
+    prometheus_metrics:
+      socket_addr: /ip4/127.0.0.1/tcp/44505/http
+    network_admin_server:
+      primary_network_admin_server_port: 45567
+      worker_network_admin_server_base_port: 43075
+    anemo:
+      send_certificate_rate_limit: ~
+      report_batch_rate_limit: ~
+      request_batches_rate_limit: ~
+enable-event-processing: false
+enable-index-processing: true
+grpc-load-shed: ~
+grpc-concurrency-limit: 20000000000
+p2p-config:
+  listen-address: "127.0.0.1:37183"
+  external-address: /ip4/127.0.0.1/udp/37183
+  state-sync:
+    checkpoint-content-timeout-ms: 10000
+genesis:
+  genesis-file-location: /root/.sui/sui_config/genesis.blob
+authority-store-pruning-config:
+  num-latest-epoch-dbs-to-retain: 3
+  epoch-db-pruning-period-secs: 3600
+  num-epochs-to-retain: 0
+  max-checkpoints-in-batch: 10
+  max-transactions-in-batch: 1000
+end-of-epoch-broadcast-channel-capacity: 128
+checkpoint-executor-config:
+  checkpoint-execution-max-concurrency: 200
+  local-execution-timeout-sec: 30
+db-checkpoint-config:
+  perform-db-checkpoints-at-epoch-end: false
+indirect-objects-threshold: 18446744073709551615
+expensive-safety-check-config:
+  enable-epoch-sui-conservation-check: false
+  enable-deep-per-tx-sui-conservation-check: false
+  force-disable-epoch-sui-conservation-check: false
+  enable-state-consistency-check: false
+  force-disable-state-consistency-check: false
+  enable-secondary-index-checks: false
+transaction-deny-config:
+  package-publish-disabled: false
+  package-upgrade-disabled: false
+  shared-object-disabled: false
+  user-transaction-disabled: false
+  receiving-objects-disabled: false
+  zklogin-sig-disabled: false
+  zklogin-disabled-providers: []
+certificate-deny-config: {}
+state-debug-dump-config: {}
+state-archive-write-config:
+  concurrency: 0
+  use-for-pruning-watermark: false
+state-archive-read-config: []
+state-snapshot-write-config:
+  concurrency: 0
+indexer-max-subscriptions: ~
+transaction-kv-store-read-config:
+  base-url: ""
+jwk-fetch-interval-seconds: 3600
+zklogin-oauth-providers:
+  Mainnet:
+    - Facebook
+    - Google
+    - Twitch
+  Testnet:
+    - Facebook
+    - Google
+    - Twitch
+  Unknown:
+    - Apple
+    - Facebook
+    - Google
+    - Kakao
+    - Slack
+    - Twitch
+authority-overload-config:
+  max-txn-age-in-queue:
+    secs: 1
+    nanos: 0
+  overload-monitor-interval:
+    secs: 10
+    nanos: 0
+  execution-queue-latency-soft-limit:
+    secs: 1
+    nanos: 0
+  execution-queue-latency-hard-limit:
+    secs: 10
+    nanos: 0
+  max-load-shedding-percentage: 95
+  min-load-shedding-percentage-above-hard-limit: 50
+  safe-transaction-ready-rate: 100

+ 125 - 0
target_chains/sui/vendor/wormhole_movement_testnet/devnet/127.0.0.1-39101.yaml

@@ -0,0 +1,125 @@
+---
+protocol-key-pair:
+  value: CyNkjqNVr3HrHTH7f/NLs7u5lUHJzuPAw0PqMTD2y2s=
+worker-key-pair:
+  value: AOuUqLZBJxwz++dkJA9sY0wvTykcCC6jSS3Jqz77IlRI
+account-key-pair:
+  value: AEUws4dzsXHsai5hVbK1O8jWOpPAJjtzdJl32Vxvoj83
+network-key-pair:
+  value: ADGySwzr54kpKui4vTatL4CtV4/1ffyyHuZ6CMyzZPGI
+db-path: /root/.sui/sui_config/authorities_db/b3fd5efb5c87
+network-address: /ip4/127.0.0.1/tcp/39101/http
+json-rpc-address: "127.0.0.1:38815"
+enable-experimental-rest-api: true
+metrics-address: "127.0.0.1:32833"
+admin-interface-port: 39835
+consensus-config:
+  address: /ip4/127.0.0.1/tcp/43831/http
+  db-path: /root/.sui/sui_config/consensus_db/b3fd5efb5c87
+  internal-worker-address: ~
+  max-pending-transactions: ~
+  max-submit-position: ~
+  submit-delay-step-override-millis: ~
+  narwhal-config:
+    header_num_of_batches_threshold: 32
+    max_header_num_of_batches: 1000
+    max_header_delay: 1000ms
+    min_header_delay: 500ms
+    gc_depth: 50
+    sync_retry_delay: 5000ms
+    sync_retry_nodes: 3
+    batch_size: 5000000
+    max_batch_delay: 100ms
+    max_concurrent_requests: 500000
+    prometheus_metrics:
+      socket_addr: /ip4/127.0.0.1/tcp/40195/http
+    network_admin_server:
+      primary_network_admin_server_port: 45269
+      worker_network_admin_server_base_port: 39967
+    anemo:
+      send_certificate_rate_limit: ~
+      report_batch_rate_limit: ~
+      request_batches_rate_limit: ~
+enable-event-processing: false
+enable-index-processing: true
+grpc-load-shed: ~
+grpc-concurrency-limit: 20000000000
+p2p-config:
+  listen-address: "127.0.0.1:36503"
+  external-address: /ip4/127.0.0.1/udp/36503
+  state-sync:
+    checkpoint-content-timeout-ms: 10000
+genesis:
+  genesis-file-location: /root/.sui/sui_config/genesis.blob
+authority-store-pruning-config:
+  num-latest-epoch-dbs-to-retain: 3
+  epoch-db-pruning-period-secs: 3600
+  num-epochs-to-retain: 0
+  max-checkpoints-in-batch: 10
+  max-transactions-in-batch: 1000
+end-of-epoch-broadcast-channel-capacity: 128
+checkpoint-executor-config:
+  checkpoint-execution-max-concurrency: 200
+  local-execution-timeout-sec: 30
+db-checkpoint-config:
+  perform-db-checkpoints-at-epoch-end: false
+indirect-objects-threshold: 18446744073709551615
+expensive-safety-check-config:
+  enable-epoch-sui-conservation-check: false
+  enable-deep-per-tx-sui-conservation-check: false
+  force-disable-epoch-sui-conservation-check: false
+  enable-state-consistency-check: false
+  force-disable-state-consistency-check: false
+  enable-secondary-index-checks: false
+transaction-deny-config:
+  package-publish-disabled: false
+  package-upgrade-disabled: false
+  shared-object-disabled: false
+  user-transaction-disabled: false
+  receiving-objects-disabled: false
+  zklogin-sig-disabled: false
+  zklogin-disabled-providers: []
+certificate-deny-config: {}
+state-debug-dump-config: {}
+state-archive-write-config:
+  concurrency: 0
+  use-for-pruning-watermark: false
+state-archive-read-config: []
+state-snapshot-write-config:
+  concurrency: 0
+indexer-max-subscriptions: ~
+transaction-kv-store-read-config:
+  base-url: ""
+jwk-fetch-interval-seconds: 3600
+zklogin-oauth-providers:
+  Mainnet:
+    - Facebook
+    - Google
+    - Twitch
+  Testnet:
+    - Facebook
+    - Google
+    - Twitch
+  Unknown:
+    - Apple
+    - Facebook
+    - Google
+    - Kakao
+    - Slack
+    - Twitch
+authority-overload-config:
+  max-txn-age-in-queue:
+    secs: 1
+    nanos: 0
+  overload-monitor-interval:
+    secs: 10
+    nanos: 0
+  execution-queue-latency-soft-limit:
+    secs: 1
+    nanos: 0
+  execution-queue-latency-hard-limit:
+    secs: 10
+    nanos: 0
+  max-load-shedding-percentage: 95
+  min-load-shedding-percentage-above-hard-limit: 50
+  safe-transaction-ready-rate: 100

+ 125 - 0
target_chains/sui/vendor/wormhole_movement_testnet/devnet/127.0.0.1-39187.yaml

@@ -0,0 +1,125 @@
+---
+protocol-key-pair:
+  value: VTDx4HjVmRBqdqBWg2zN+zcFE20io3CrBchGy/iV1lo=
+worker-key-pair:
+  value: ACsedxHqp9Son+iep5m4+eKM+yMc8hYyqhrDJLUucJ+G
+account-key-pair:
+  value: AAAujq3QBAO4JNOYeKBW5dMn+8N4zE4bEHx+Bv9Y5tKr
+network-key-pair:
+  value: AOFPA8/e6v4OpU5U0308llf51JfsxVla/pclVq9Ztajb
+db-path: /root/.sui/sui_config/authorities_db/99f25ef61f80
+network-address: /ip4/127.0.0.1/tcp/39187/http
+json-rpc-address: "127.0.0.1:33519"
+enable-experimental-rest-api: true
+metrics-address: "127.0.0.1:33765"
+admin-interface-port: 33957
+consensus-config:
+  address: /ip4/127.0.0.1/tcp/41413/http
+  db-path: /root/.sui/sui_config/consensus_db/99f25ef61f80
+  internal-worker-address: ~
+  max-pending-transactions: ~
+  max-submit-position: ~
+  submit-delay-step-override-millis: ~
+  narwhal-config:
+    header_num_of_batches_threshold: 32
+    max_header_num_of_batches: 1000
+    max_header_delay: 1000ms
+    min_header_delay: 500ms
+    gc_depth: 50
+    sync_retry_delay: 5000ms
+    sync_retry_nodes: 3
+    batch_size: 5000000
+    max_batch_delay: 100ms
+    max_concurrent_requests: 500000
+    prometheus_metrics:
+      socket_addr: /ip4/127.0.0.1/tcp/35645/http
+    network_admin_server:
+      primary_network_admin_server_port: 44333
+      worker_network_admin_server_base_port: 43351
+    anemo:
+      send_certificate_rate_limit: ~
+      report_batch_rate_limit: ~
+      request_batches_rate_limit: ~
+enable-event-processing: false
+enable-index-processing: true
+grpc-load-shed: ~
+grpc-concurrency-limit: 20000000000
+p2p-config:
+  listen-address: "127.0.0.1:40869"
+  external-address: /ip4/127.0.0.1/udp/40869
+  state-sync:
+    checkpoint-content-timeout-ms: 10000
+genesis:
+  genesis-file-location: /root/.sui/sui_config/genesis.blob
+authority-store-pruning-config:
+  num-latest-epoch-dbs-to-retain: 3
+  epoch-db-pruning-period-secs: 3600
+  num-epochs-to-retain: 0
+  max-checkpoints-in-batch: 10
+  max-transactions-in-batch: 1000
+end-of-epoch-broadcast-channel-capacity: 128
+checkpoint-executor-config:
+  checkpoint-execution-max-concurrency: 200
+  local-execution-timeout-sec: 30
+db-checkpoint-config:
+  perform-db-checkpoints-at-epoch-end: false
+indirect-objects-threshold: 18446744073709551615
+expensive-safety-check-config:
+  enable-epoch-sui-conservation-check: false
+  enable-deep-per-tx-sui-conservation-check: false
+  force-disable-epoch-sui-conservation-check: false
+  enable-state-consistency-check: false
+  force-disable-state-consistency-check: false
+  enable-secondary-index-checks: false
+transaction-deny-config:
+  package-publish-disabled: false
+  package-upgrade-disabled: false
+  shared-object-disabled: false
+  user-transaction-disabled: false
+  receiving-objects-disabled: false
+  zklogin-sig-disabled: false
+  zklogin-disabled-providers: []
+certificate-deny-config: {}
+state-debug-dump-config: {}
+state-archive-write-config:
+  concurrency: 0
+  use-for-pruning-watermark: false
+state-archive-read-config: []
+state-snapshot-write-config:
+  concurrency: 0
+indexer-max-subscriptions: ~
+transaction-kv-store-read-config:
+  base-url: ""
+jwk-fetch-interval-seconds: 3600
+zklogin-oauth-providers:
+  Mainnet:
+    - Facebook
+    - Google
+    - Twitch
+  Testnet:
+    - Facebook
+    - Google
+    - Twitch
+  Unknown:
+    - Apple
+    - Facebook
+    - Google
+    - Kakao
+    - Slack
+    - Twitch
+authority-overload-config:
+  max-txn-age-in-queue:
+    secs: 1
+    nanos: 0
+  overload-monitor-interval:
+    secs: 10
+    nanos: 0
+  execution-queue-latency-soft-limit:
+    secs: 1
+    nanos: 0
+  execution-queue-latency-hard-limit:
+    secs: 10
+    nanos: 0
+  max-load-shedding-percentage: 95
+  min-load-shedding-percentage-above-hard-limit: 50
+  safe-transaction-ready-rate: 100

+ 12 - 0
target_chains/sui/vendor/wormhole_movement_testnet/devnet/client.yaml

@@ -0,0 +1,12 @@
+---
+keystore:
+  File: /root/.sui/sui_config/sui.keystore
+envs:
+  - alias: localnet
+    rpc: "http://0.0.0.0:9000"
+    ws: ~
+  - alias: devnet
+    rpc: "https://fullnode.devnet.sui.io:443"
+    ws: ~
+active_env: localnet
+active_address: ~

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 34 - 0
target_chains/sui/vendor/wormhole_movement_testnet/devnet/fullnode.yaml


BIN=BIN
target_chains/sui/vendor/wormhole_movement_testnet/devnet/genesis.blob


+ 55 - 0
target_chains/sui/vendor/wormhole_movement_testnet/devnet/genesis_config

@@ -0,0 +1,55 @@
+---
+ssfn_config_info: ~
+validator_config_info: ~
+parameters:
+  chain_start_timestamp_ms: 1709486339140
+  protocol_version: 36
+  allow_insertion_of_extra_objects: true
+  epoch_duration_ms: 86400000
+  stake_subsidy_start_epoch: 0
+  stake_subsidy_initial_distribution_amount: 1000000000000000
+  stake_subsidy_period_length: 10
+  stake_subsidy_decrease_rate: 1000
+accounts:
+  - address: "0x2e425dd30f43ff1d59547322839cfc4b8fbaae54d72075181ebd7388b644fdbe"
+    gas_amounts:
+      - 30000000000000000
+      - 30000000000000000
+      - 30000000000000000
+      - 30000000000000000
+      - 30000000000000000
+  - address: "0x8a8bb058d6c86aa175566c9e2d19278dd22ed9fecdda8fb486018f93a0629bb5"
+    gas_amounts:
+      - 30000000000000000
+      - 30000000000000000
+      - 30000000000000000
+      - 30000000000000000
+      - 30000000000000000
+  - address: "0xa0f33ce147ecae789f535c64634851724284dd618a529672702b991a5f7bf816"
+    gas_amounts:
+      - 30000000000000000
+      - 30000000000000000
+      - 30000000000000000
+      - 30000000000000000
+      - 30000000000000000
+  - address: "0xbd00a48078c0513a5f9a0d1c9352cd5c23a0e0cf3e6a82673cdae857cd00021e"
+    gas_amounts:
+      - 30000000000000000
+      - 30000000000000000
+      - 30000000000000000
+      - 30000000000000000
+      - 30000000000000000
+  - address: "0xfd5f84cf9285f2b206e03727224b9daffa6092661b840d92434751792010b7de"
+    gas_amounts:
+      - 30000000000000000
+      - 30000000000000000
+      - 30000000000000000
+      - 30000000000000000
+      - 30000000000000000
+  - address: "0xed867315e3f7c83ae82e6d5858b6a6cc57c291fd84f7509646ebc8162169cf96"
+    gas_amounts:
+      - 30000000000000000
+      - 30000000000000000
+      - 30000000000000000
+      - 30000000000000000
+      - 30000000000000000

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 499 - 0
target_chains/sui/vendor/wormhole_movement_testnet/devnet/network.yaml


+ 1 - 0
target_chains/sui/vendor/wormhole_movement_testnet/examples/coins/.gitignore

@@ -0,0 +1 @@
+build

+ 15 - 0
target_chains/sui/vendor/wormhole_movement_testnet/examples/coins/Makefile

@@ -0,0 +1,15 @@
+.PHONY: all clean test check
+
+all: check
+
+.PHONY: clean
+clean:
+	rm -rf build
+
+.PHONY: check
+check:
+	sui move build -d
+
+.PHONY: test
+test: check
+	sui move test -d

+ 17 - 0
target_chains/sui/vendor/wormhole_movement_testnet/examples/coins/Move.devnet.toml

@@ -0,0 +1,17 @@
+[package]
+name = "Coins"
+version = "0.1.0"
+
+[dependencies.Sui]
+git = "https://github.com/MystenLabs/sui.git"
+subdir = "crates/sui-framework/packages/sui-framework"
+rev = "041c5f2bae2fe52079e44b70514333532d69f4e6"
+
+[dependencies.Wormhole]
+local = "../../wormhole"
+
+[dependencies.TokenBridge]
+local = "../../token_bridge"
+
+[addresses]
+coins = "_"

+ 52 - 0
target_chains/sui/vendor/wormhole_movement_testnet/examples/coins/Move.lock

@@ -0,0 +1,52 @@
+# @generated by Move, please check-in and do not edit manually.
+
+[move]
+version = 0
+manifest_digest = "F1027436A2346E82F07F1149F91C26F61778F611858CEA83F9D22BDEF50A7FD8"
+deps_digest = "397E6A9F7A624706DBDFEE056CE88391A15876868FD18A88504DA74EB458D697"
+
+dependencies = [
+  { name = "Sui" },
+]
+
+dev-dependencies = [
+  { name = "TokenBridge" },
+  { name = "Wormhole" },
+]
+
+[[move.package]]
+name = "MoveStdlib"
+source = { git = "https://github.com/MystenLabs/sui.git", rev = "041c5f2bae2fe52079e44b70514333532d69f4e6", subdir = "crates/sui-framework/packages/move-stdlib" }
+
+[[move.package]]
+name = "Sui"
+source = { git = "https://github.com/MystenLabs/sui.git", rev = "041c5f2bae2fe52079e44b70514333532d69f4e6", subdir = "crates/sui-framework/packages/sui-framework" }
+
+dependencies = [
+  { name = "MoveStdlib" },
+]
+
+[[move.package]]
+name = "TokenBridge"
+source = { local = "../../token_bridge" }
+
+dependencies = [
+  { name = "Sui" },
+]
+
+dev-dependencies = [
+  { name = "Wormhole" },
+]
+
+[[move.package]]
+name = "Wormhole"
+source = { local = "../../wormhole" }
+
+dependencies = [
+  { name = "Sui" },
+]
+
+[move.toolchain-version]
+compiler-version = "1.19.0"
+edition = "legacy"
+flavor = "sui"

+ 28 - 0
target_chains/sui/vendor/wormhole_movement_testnet/examples/coins/Move.toml

@@ -0,0 +1,28 @@
+[package]
+name = "Coins"
+version = "0.1.0"
+
+[dependencies.Sui]
+git = "https://github.com/MystenLabs/sui.git"
+subdir = "crates/sui-framework/packages/sui-framework"
+rev = "041c5f2bae2fe52079e44b70514333532d69f4e6"
+
+[dependencies.Wormhole]
+local = "../../wormhole"
+
+[dependencies.TokenBridge]
+local = "../../token_bridge"
+
+[addresses]
+coins = "_"
+
+[dev-dependencies.Wormhole]
+local = "../../wormhole"
+
+[dev-dependencies.TokenBridge]
+local = "../../token_bridge"
+
+[dev-addresses]
+wormhole = "0x100"
+token_bridge = "0x200"
+coins = "0x20c"

+ 210 - 0
target_chains/sui/vendor/wormhole_movement_testnet/examples/coins/sources/coin.move

@@ -0,0 +1,210 @@
+// Example wrapped coin for testing purposes
+
+#[test_only]
+module coins::coin {
+    use sui::object::{Self};
+    use sui::package::{Self};
+    use sui::transfer::{Self};
+    use sui::tx_context::{Self, TxContext};
+
+    use token_bridge::create_wrapped::{Self};
+
+    struct COIN has drop {}
+
+    fun init(witness: COIN, ctx: &mut TxContext) {
+        use token_bridge::version_control::{V__0_2_0 as V__CURRENT};
+
+        transfer::public_transfer(
+            create_wrapped::prepare_registration<COIN, V__CURRENT>(
+                witness,
+                // TODO: create a version of this for each decimal to be used
+                8,
+                ctx
+            ),
+            tx_context::sender(ctx)
+        );
+    }
+
+    #[test_only]
+    /// NOTE: Even though this module is `#[test_only]`, this method is tagged
+    /// with the same macro  as a trick to allow another method within this
+    /// module to call `init` using OTW.
+    public fun init_test_only(ctx: &mut TxContext) {
+        init(COIN {}, ctx);
+
+        // This will be created and sent to the transaction sender
+        // automatically when the contract is published.
+        transfer::public_transfer(
+            package::test_publish(object::id_from_address(@coins), ctx),
+            tx_context::sender(ctx)
+        );
+    }
+}
+
+#[test_only]
+module coins::coin_tests {
+    use sui::coin::{Self};
+    use sui::package::{UpgradeCap};
+    use sui::test_scenario::{Self};
+    use token_bridge::create_wrapped::{Self, WrappedAssetSetup};
+    use token_bridge::state::{Self};
+    use token_bridge::token_bridge_scenario::{
+        register_dummy_emitter,
+        return_state,
+        set_up_wormhole_and_token_bridge,
+        take_state,
+        two_people
+    };
+    use token_bridge::token_registry::{Self};
+    use token_bridge::vaa::{Self};
+    use token_bridge::wrapped_asset::{Self};
+    use wormhole::bytes32::{Self};
+    use wormhole::external_address::{Self};
+    use wormhole::wormhole_scenario::{parse_and_verify_vaa};
+
+    use token_bridge::version_control::{V__0_2_0 as V__CURRENT};
+
+    use coins::coin::{COIN};
+
+// +------------------------------------------------------------------------------+
+// | Wormhole VAA v1         | nonce: 1                | time: 1                  |
+// | guardian set #0         | #22080291               | consistency: 0           |
+// |------------------------------------------------------------------------------|
+// | Signature:                                                                   |
+// |   #0: 80366065746148420220f25a6275097370e8db40984529a6676b7a5fc9fe...        |
+// |------------------------------------------------------------------------------|
+// | Emitter: 0x00000000000000000000000000000000deadbeef (Ethereum)               |
+// |==============================================================================|
+// | Token attestation                                                            |
+// | decimals: 12                                                                 |
+// | Token: 0x00000000000000000000000000000000beefface (Ethereum)                 |
+// | Symbol: BEEF                                                                 |
+// | Name: Beef face Token                                                        |
+// +------------------------------------------------------------------------------+
+    const VAA: vector<u8> =
+        x"0100000000010080366065746148420220f25a6275097370e8db40984529a6676b7a5fc9feb11755ec49ca626b858ddfde88d15601f85ab7683c5f161413b0412143241c700aff010000000100000001000200000000000000000000000000000000000000000000000000000000deadbeef000000000150eb23000200000000000000000000000000000000000000000000000000000000beefface00020c424545460000000000000000000000000000000000000000000000000000000042656566206661636520546f6b656e0000000000000000000000000000000000";
+
+// +------------------------------------------------------------------------------+
+// | Wormhole VAA v1         | nonce: 69               | time: 0                  |
+// | guardian set #0         | #1                      | consistency: 15          |
+// |------------------------------------------------------------------------------|
+// | Signature:                                                                   |
+// |   #0: b0571650590e147fce4eb60105e0463522c1244a97bd5dcb365d3e7bc7f3...        |
+// |------------------------------------------------------------------------------|
+// | Emitter: 0x00000000000000000000000000000000deadbeef (Ethereum)               |
+// |==============================================================================|
+// | Token attestation                                                            |
+// | decimals: 12                                                                 |
+// | Token: 0x00000000000000000000000000000000beefface (Ethereum)                 |
+// | Symbol: BEEF??? and profit                                                   |
+// | Name: Beef face Token??? and profit                                          |
+// +------------------------------------------------------------------------------+
+    const UPDATED_VAA: vector<u8> =
+        x"0100000000010062f4dcd21bbbc4af8b8baaa2da3a0b168efc4c975de5b828c7a3c710b67a0a0d476d10a74aba7a7867866daf97d1372d8e6ee62ccc5ae522e3e603c67fa23787000000000000000045000200000000000000000000000000000000000000000000000000000000deadbeef00000000000000010f0200000000000000000000000000000000000000000000000000000000beefface00020c424545463f3f3f20616e642070726f666974000000000000000000000000000042656566206661636520546f6b656e3f3f3f20616e642070726f666974000000";
+
+
+    #[test]
+    public fun test_complete_and_update_attestation() {
+        let (caller, coin_deployer) = two_people();
+        let my_scenario = test_scenario::begin(caller);
+        let scenario = &mut my_scenario;
+
+        // Set up contracts.
+        let wormhole_fee = 350;
+        set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
+
+        // Register foreign emitter on chain ID == 2.
+        let expected_source_chain = 2;
+        register_dummy_emitter(scenario, expected_source_chain);
+
+        // Ignore effects. Make sure `coin_deployer` receives
+        // `WrappedAssetSetup`.
+        test_scenario::next_tx(scenario, coin_deployer);
+
+        // Publish coin.
+        coins::coin::init_test_only(test_scenario::ctx(scenario));
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, coin_deployer);
+
+        let wrapped_asset_setup =
+            test_scenario::take_from_address<WrappedAssetSetup<COIN, V__CURRENT>>(
+                scenario,
+                coin_deployer
+            );
+
+        let token_bridge_state = take_state(scenario);
+
+        let verified_vaa = parse_and_verify_vaa(scenario, VAA);
+        let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa);
+
+        let coin_meta = test_scenario::take_shared(scenario);
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, caller);
+
+        create_wrapped::complete_registration(
+            &mut token_bridge_state,
+            &mut coin_meta,
+            wrapped_asset_setup,
+            test_scenario::take_from_address<UpgradeCap>(
+                scenario,
+                coin_deployer
+            ),
+            msg
+        );
+
+        // Check registry.
+        {
+            let verified = state::verified_asset<COIN>(&token_bridge_state);
+            assert!(token_bridge::token_registry::is_wrapped<COIN>(&verified), 0);
+
+            let registry = state::borrow_token_registry(&token_bridge_state);
+            let asset =
+                token_registry::borrow_wrapped<COIN>(registry);
+            assert!(wrapped_asset::total_supply(asset) == 0, 0);
+
+            // Decimals are capped for this wrapped asset.
+            assert!(coin::get_decimals(&coin_meta) == 8, 0);
+
+            // Check metadata against asset metadata.
+            let info = wrapped_asset::info(asset);
+            assert!(wrapped_asset::token_chain(info) == 2, 0);
+            assert!(wrapped_asset::token_address(info) == external_address::new(bytes32::from_bytes(x"00000000000000000000000000000000beefface")), 0);
+            assert!(
+                wrapped_asset::native_decimals(info) == 12,
+                0
+            );
+            assert!(coin::get_symbol(&coin_meta) == std::ascii::string(b"BEEF"), 0);
+            assert!(coin::get_name(&coin_meta) == std::string::utf8(b"Beef face Token"), 0);
+        };
+
+        let verified_vaa =
+            parse_and_verify_vaa(scenario, UPDATED_VAA);
+        let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa);
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, caller);
+
+        // Now update metadata.
+        create_wrapped::update_attestation<COIN>(&mut token_bridge_state, &mut coin_meta, msg);
+
+        // Check updated name and symbol.
+        assert!(
+            coin::get_name(&coin_meta) == std::string::utf8(b"Beef face Token??? and profit"),
+            0
+        );
+        assert!(
+            coin::get_symbol(&coin_meta) == std::ascii::string(b"BEEF??? and profit"),
+            0
+        );
+
+        // Clean up.
+        return_state(token_bridge_state);
+        test_scenario::return_shared(coin_meta);
+
+
+        // Done.
+        test_scenario::end(my_scenario);
+    }
+}

+ 72 - 0
target_chains/sui/vendor/wormhole_movement_testnet/examples/coins/sources/coin_10.move

@@ -0,0 +1,72 @@
+module coins::coin_10 {
+    use std::option;
+    use sui::coin::{Self, TreasuryCap, CoinMetadata};
+    use sui::transfer;
+    use sui::tx_context::{Self, TxContext};
+
+    /// The type identifier of coin. The coin will have a type
+    /// tag of kind: `Coin<package_object::coin_10::COIN_10>`
+    /// Make sure that the name of the type matches the module's name.
+    struct COIN_10 has drop {}
+
+    /// Module initializer is called once on module publish. A treasury
+    /// cap is sent to the publisher, who then controls minting and burning
+    fun init(witness: COIN_10, ctx: &mut TxContext) {
+        let (treasury, metadata) = create_coin(witness, ctx);
+        transfer::public_freeze_object(metadata);
+        transfer::public_transfer(treasury, tx_context::sender(ctx));
+    }
+
+    fun create_coin(
+        witness: COIN_10,
+        ctx: &mut TxContext
+    ): (TreasuryCap<COIN_10>, CoinMetadata<COIN_10>) {
+        coin::create_currency(
+            witness,
+            10, // decimals
+            b"COIN_10", // symbol
+            b"10-Decimal Coin", // name
+            b"", // description
+            option::none(), // icon_url
+            ctx
+        )
+    }
+
+    #[test_only]
+    public fun create_coin_test_only(
+        ctx: &mut TxContext
+    ): (TreasuryCap<COIN_10>, CoinMetadata<COIN_10>) {
+        create_coin(COIN_10 {}, ctx)
+    }
+
+    #[test_only]
+    public fun init_test_only(ctx: &mut TxContext) {
+        init(COIN_10 {}, ctx)
+    }
+}
+
+#[test_only]
+module coins::coin_10_tests {
+    use sui::test_scenario::{Self};
+
+    use coins::coin_10::{Self};
+
+    #[test]
+    public fun init_test() {
+        let my_scenario = test_scenario::begin(@0x0);
+        let scenario = &mut my_scenario;
+        let creator = @0xDEADBEEF;
+
+        // Proceed.
+        test_scenario::next_tx(scenario, creator);
+
+        // Init.
+        coin_10::init_test_only(test_scenario::ctx(scenario));
+
+        // Proceed.
+        test_scenario::next_tx(scenario, creator);
+
+        // Done.
+        test_scenario::end(my_scenario);
+    }
+}

+ 72 - 0
target_chains/sui/vendor/wormhole_movement_testnet/examples/coins/sources/coin_8.move

@@ -0,0 +1,72 @@
+module coins::coin_8 {
+    use std::option::{Self};
+    use sui::coin::{Self, TreasuryCap, CoinMetadata};
+    use sui::transfer::{Self};
+    use sui::tx_context::{Self, TxContext};
+
+    /// The type identifier of coin. The coin will have a type
+    /// tag of kind: `Coin<package_object::coin_8::COIN_8>`
+    /// Make sure that the name of the type matches the module's name.
+    struct COIN_8 has drop {}
+
+    /// Module initializer is called once on module publish. A treasury
+    /// cap is sent to the publisher, who then controls minting and burning
+    fun init(witness: COIN_8, ctx: &mut TxContext) {
+        let (treasury, metadata) = create_coin(witness, ctx);
+        transfer::public_freeze_object(metadata);
+        transfer::public_transfer(treasury, tx_context::sender(ctx));
+    }
+
+    fun create_coin(
+        witness: COIN_8,
+        ctx: &mut TxContext
+    ): (TreasuryCap<COIN_8>, CoinMetadata<COIN_8>) {
+        coin::create_currency(
+            witness,
+            8, // decimals
+            b"COIN_8", // symbol
+            b"8-Decimal Coin", // name
+            b"", // description
+            option::none(), // icon_url
+            ctx
+        )
+    }
+
+    #[test_only]
+    public fun create_coin_test_only(
+        ctx: &mut TxContext
+    ): (TreasuryCap<COIN_8>, CoinMetadata<COIN_8>) {
+        create_coin(COIN_8 {}, ctx)
+    }
+
+    #[test_only]
+    public fun init_test_only(ctx: &mut TxContext) {
+        init(COIN_8 {}, ctx)
+    }
+}
+
+#[test_only]
+module coins::coin_8_tests {
+    use sui::test_scenario::{Self};
+
+    use coins::coin_8::{Self};
+
+    #[test]
+    public fun init_test() {
+        let my_scenario = test_scenario::begin(@0x0);
+        let scenario = &mut my_scenario;
+        let creator = @0xDEADBEEF;
+
+        // Proceed.
+        test_scenario::next_tx(scenario, creator);
+
+        // Init.
+        coin_8::init_test_only(test_scenario::ctx(scenario));
+
+        // Proceed.
+        test_scenario::next_tx(scenario, creator);
+
+        // Done.
+        test_scenario::end(my_scenario);
+    }
+}

+ 20 - 0
target_chains/sui/vendor/wormhole_movement_testnet/examples/core_messages/Makefile

@@ -0,0 +1,20 @@
+-include ../../../Makefile.help
+
+.PHONY: artifacts
+artifacts: clean
+
+.PHONY: clean
+# Clean build artifacts
+clean:
+	rm -rf build
+
+.PHONY: build
+# Build contract
+build:
+	sui move build
+
+.PHONY: test
+# Run tests
+test:
+	sui move build -d || exit $?
+	sui move test -t 1

+ 14 - 0
target_chains/sui/vendor/wormhole_movement_testnet/examples/core_messages/Move.devnet.toml

@@ -0,0 +1,14 @@
+[package]
+name = "CoreMessages"
+version = "1.0.0"
+
+[dependencies.Sui]
+git = "https://github.com/MystenLabs/sui.git"
+subdir = "crates/sui-framework/packages/sui-framework"
+rev = "041c5f2bae2fe52079e44b70514333532d69f4e6"
+
+[dependencies.Wormhole]
+local = "../../wormhole"
+
+[addresses]
+core_messages = "_"

+ 39 - 0
target_chains/sui/vendor/wormhole_movement_testnet/examples/core_messages/Move.lock

@@ -0,0 +1,39 @@
+# @generated by Move, please check-in and do not edit manually.
+
+[move]
+version = 0
+manifest_digest = "E0D2B32F0A5B6F9A76311FD7A68260A698BD9ECCEAF95A779183CB374EC933FB"
+deps_digest = "060AD7E57DFB13104F21BE5F5C3759D03F0553FC3229247D9A7A6B45F50D03A3"
+
+dependencies = [
+  { name = "Sui" },
+]
+
+dev-dependencies = [
+  { name = "Wormhole" },
+]
+
+[[move.package]]
+name = "MoveStdlib"
+source = { git = "https://github.com/MystenLabs/sui.git", rev = "041c5f2bae2fe52079e44b70514333532d69f4e6", subdir = "crates/sui-framework/packages/move-stdlib" }
+
+[[move.package]]
+name = "Sui"
+source = { git = "https://github.com/MystenLabs/sui.git", rev = "041c5f2bae2fe52079e44b70514333532d69f4e6", subdir = "crates/sui-framework/packages/sui-framework" }
+
+dependencies = [
+  { name = "MoveStdlib" },
+]
+
+[[move.package]]
+name = "Wormhole"
+source = { local = "../../wormhole" }
+
+dependencies = [
+  { name = "Sui" },
+]
+
+[move.toolchain-version]
+compiler-version = "1.19.0"
+edition = "legacy"
+flavor = "sui"

+ 21 - 0
target_chains/sui/vendor/wormhole_movement_testnet/examples/core_messages/Move.toml

@@ -0,0 +1,21 @@
+[package]
+name = "CoreMessages"
+version = "1.0.0"
+
+[dependencies.Sui]
+git = "https://github.com/MystenLabs/sui.git"
+subdir = "crates/sui-framework/packages/sui-framework"
+rev = "041c5f2bae2fe52079e44b70514333532d69f4e6"
+
+[dependencies.Wormhole]
+local = "../../wormhole"
+
+[addresses]
+core_messages = "_"
+
+[dev-dependencies.Wormhole]
+local = "../../wormhole"
+
+[dev-addresses]
+wormhole = "0x100"
+core_messages = "0x169"

+ 149 - 0
target_chains/sui/vendor/wormhole_movement_testnet/examples/core_messages/sources/sender.move

@@ -0,0 +1,149 @@
+/// A simple contracts that demonstrates how to send messages with wormhole.
+module core_messages::sender {
+    use sui::clock::{Clock};
+    use sui::coin::{Self};
+    use sui::object::{Self, UID};
+    use sui::transfer::{Self};
+    use sui::tx_context::{TxContext};
+    use wormhole::emitter::{Self, EmitterCap};
+    use wormhole::state::{State as WormholeState};
+
+    struct State has key, store {
+        id: UID,
+        emitter_cap: EmitterCap,
+    }
+
+    /// Register ourselves as a wormhole emitter. This gives back an
+    /// `EmitterCap` which will be required to send messages through
+    /// wormhole.
+    public fun init_with_params(
+        wormhole_state: &WormholeState,
+        ctx: &mut TxContext
+    ) {
+        transfer::share_object(
+            State {
+                id: object::new(ctx),
+                emitter_cap: emitter::new(wormhole_state, ctx)
+            }
+        );
+    }
+
+    public fun send_message_entry(
+        state: &mut State,
+        wormhole_state: &mut WormholeState,
+        payload: vector<u8>,
+        the_clock: &Clock,
+        ctx: &mut TxContext
+    ) {
+        send_message(
+            state,
+            wormhole_state,
+            payload,
+            the_clock,
+            ctx
+        );
+    }
+
+    /// NOTE: This is NOT the proper way of using the `prepare_message` and
+    /// `publish_message` workflow. This example app is meant for testing for
+    /// observing Wormhole messages via the guardian.
+    ///
+    /// See `publish_message` module for more info.
+    public fun send_message(
+        state: &mut State,
+        wormhole_state: &mut WormholeState,
+        payload: vector<u8>,
+        the_clock: &Clock,
+        ctx: &mut TxContext
+    ): u64 {
+        use wormhole::publish_message::{prepare_message, publish_message};
+
+        // NOTE AGAIN: Integrators should NEVER call this within their contract.
+        publish_message(
+            wormhole_state,
+            coin::zero(ctx),
+            prepare_message(
+                &mut state.emitter_cap,
+                0, // Set nonce to 0, intended for batch VAAs.
+                payload
+            ),
+            the_clock
+        )
+    }
+}
+
+#[test_only]
+module core_messages::sender_test {
+    use sui::test_scenario::{Self};
+    use wormhole::wormhole_scenario::{
+        return_clock,
+        return_state,
+        set_up_wormhole,
+        take_clock,
+        take_state,
+        two_people,
+    };
+
+    use core_messages::sender::{
+        State,
+        init_with_params,
+        send_message,
+    };
+
+    #[test]
+    public fun test_send_message() {
+        let (user, admin) = two_people();
+        let my_scenario = test_scenario::begin(admin);
+        let scenario = &mut my_scenario;
+
+        // Initialize Wormhole.
+        let wormhole_message_fee = 0;
+        set_up_wormhole(scenario, wormhole_message_fee);
+
+        // Initialize sender module.
+        test_scenario::next_tx(scenario, admin);
+        {
+            let wormhole_state = take_state(scenario);
+            init_with_params(&wormhole_state, test_scenario::ctx(scenario));
+            return_state(wormhole_state);
+        };
+
+        // Send message as an ordinary user.
+        test_scenario::next_tx(scenario, user);
+        {
+            let state = test_scenario::take_shared<State>(scenario);
+            let wormhole_state = take_state(scenario);
+            let the_clock = take_clock(scenario);
+
+            let first_message_sequence = send_message(
+                &mut state,
+                &mut wormhole_state,
+                b"Hello",
+                &the_clock,
+                test_scenario::ctx(scenario)
+            );
+            assert!(first_message_sequence == 0, 0);
+
+            let second_message_sequence = send_message(
+                &mut state,
+                &mut wormhole_state,
+                b"World",
+                &the_clock,
+                test_scenario::ctx(scenario)
+            );
+            assert!(second_message_sequence == 1, 0);
+
+            // Clean up.
+            test_scenario::return_shared(state);
+            return_state(wormhole_state);
+            return_clock(the_clock);
+        };
+
+        // Check effects.
+        let effects = test_scenario::next_tx(scenario, user);
+        assert!(test_scenario::num_user_events(&effects) == 2, 0);
+
+        // End test.
+        test_scenario::end(my_scenario);
+    }
+}

+ 3 - 0
target_chains/sui/vendor/wormhole_movement_testnet/examples/templates/README.md

@@ -0,0 +1,3 @@
+# Templates
+
+This directory contains templates for Sui contracts. These templates aren't fully functional contracts and require substitution of variables prior to deployment. For example, the `wrapped_coin` template requires the version control struct name as well as the decimals of the wrapped token.

+ 19 - 0
target_chains/sui/vendor/wormhole_movement_testnet/examples/templates/wrapped_coin/Move.toml

@@ -0,0 +1,19 @@
+[package]
+name = "WrappedCoin"
+version = "0.0.1"
+
+[dependencies.Sui]
+git = "https://github.com/MystenLabs/sui.git"
+subdir = "crates/sui-framework/packages/sui-framework"
+rev = "041c5f2bae2fe52079e44b70514333532d69f4e6"
+
+[dependencies.Wormhole]
+local = "../../wormhole"
+
+[dependencies.TokenBridge]
+local = "../../token_bridge"
+
+[addresses]
+wormhole = "_"
+token_bridge = "_"
+wrapped_coin = "0x0"

+ 21 - 0
target_chains/sui/vendor/wormhole_movement_testnet/examples/templates/wrapped_coin/sources/coin.move

@@ -0,0 +1,21 @@
+module wrapped_coin::coin {
+    use sui::transfer::{Self};
+    use sui::tx_context::{Self, TxContext};
+
+    use token_bridge::create_wrapped::{Self};
+
+    struct COIN has drop {}
+
+    fun init(witness: COIN, ctx: &mut TxContext) {
+        use token_bridge::version_control::{{{VERSION}}};
+
+        transfer::public_transfer(
+            create_wrapped::prepare_registration<COIN, {{VERSION}}>(
+                witness,
+                {{DECIMALS}},
+                ctx
+            ),
+            tx_context::sender(ctx)
+        );
+    }
+}

+ 119 - 0
target_chains/sui/vendor/wormhole_movement_testnet/scripts/deploy.sh

@@ -0,0 +1,119 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+# Help message
+function usage() {
+cat <<EOF >&2
+Deploy and initialize Sui core bridge and token bridge contracts to the
+specified network. Additionally deploys an example messaging contract in
+devnet.
+
+  Usage: $(basename "$0") <network> [options]
+
+  Positional args:
+    <network>          Network to deploy to (devnet, testnet, mainnet)
+
+  Options:
+    -k, --private-key  Use given key to sign transactions
+    -h, --help         Show this help message
+EOF
+exit 1
+}
+
+# If positional args are missing, print help message and exit
+if [ $# -lt 1 ]; then
+  usage
+fi
+
+# Default values
+PRIVATE_KEY_ARG=
+
+# Set network
+NETWORK=$1 || usage
+shift
+
+# Set guardian address
+if [ "$NETWORK" = mainnet ]; then
+  echo "Mainnet not supported yet"
+  exit 1
+elif [ "$NETWORK" = testnet ]; then
+  echo "Testnet not supported yet"
+  exit 1
+elif [ "$NETWORK" = devnet ]; then
+  GUARDIAN_ADDR=befa429d57cd18b7f8a4d91a2da9ab4af05d0fbe
+else
+  usage
+fi
+
+# Parse short/long flags
+while [[ $# -gt 0 ]]; do
+  case "$1" in
+    -k|--private-key)
+      if [[ ! -z "$2" ]]; then
+        PRIVATE_KEY_ARG="-k $2"
+      fi
+      shift 2
+      ;;
+    -h|--help)
+      usage
+      exit 0
+      ;;
+    *)
+      echo "Unknown option: $1"
+      usage
+      exit 1
+      ;;
+  esac
+done
+
+# Assumes this script is in a sibling directory to contract dirs
+DIRNAME=$(dirname "$0")
+WORMHOLE_PATH=$(realpath "$DIRNAME"/../wormhole)
+TOKEN_BRIDGE_PATH=$(realpath "$DIRNAME"/../token_bridge)
+EXAMPLE_APP_PATH=$(realpath "$DIRNAME"/../examples/core_messages)
+EXAMPLE_COIN_PATH=$(realpath "$DIRNAME"/../examples/coins)
+
+echo -e "[1/4] Publishing core bridge contracts..."
+WORMHOLE_PUBLISH_OUTPUT=$($(echo worm sui deploy "$WORMHOLE_PATH" -n "$NETWORK" "$PRIVATE_KEY_ARG"))
+echo "$WORMHOLE_PUBLISH_OUTPUT"
+
+echo -e "\n[2/4] Initializing core bridge..."
+WORMHOLE_PACKAGE_ID=$(echo "$WORMHOLE_PUBLISH_OUTPUT" | grep -oP 'Published to +\K.*')
+WORMHOLE_INIT_OUTPUT=$($(echo worm sui init-wormhole -n "$NETWORK" --initial-guardian "$GUARDIAN_ADDR" -p "$WORMHOLE_PACKAGE_ID" "$PRIVATE_KEY_ARG"))
+WORMHOLE_STATE_OBJECT_ID=$(echo "$WORMHOLE_INIT_OUTPUT" | grep -oP 'Wormhole state object ID +\K.*')
+echo "$WORMHOLE_INIT_OUTPUT"
+
+echo -e "\n[3/4] Publishing token bridge contracts..."
+TOKEN_BRIDGE_PUBLISH_OUTPUT=$($(echo worm sui deploy "$TOKEN_BRIDGE_PATH" -n "$NETWORK" "$PRIVATE_KEY_ARG"))
+echo "$TOKEN_BRIDGE_PUBLISH_OUTPUT"
+
+echo -e "\n[4/4] Initializing token bridge..."
+TOKEN_BRIDGE_PACKAGE_ID=$(echo "$TOKEN_BRIDGE_PUBLISH_OUTPUT" | grep -oP 'Published to +\K.*')
+TOKEN_BRIDGE_INIT_OUTPUT=$($(echo worm sui init-token-bridge -n "$NETWORK" -p "$TOKEN_BRIDGE_PACKAGE_ID" -w "$WORMHOLE_STATE_OBJECT_ID" "$PRIVATE_KEY_ARG"))
+TOKEN_BRIDGE_STATE_OBJECT_ID=$(echo "$TOKEN_BRIDGE_INIT_OUTPUT" | grep -oP 'Token bridge state object ID +\K.*')
+echo "$TOKEN_BRIDGE_INIT_OUTPUT"
+
+if [ "$NETWORK" = devnet ]; then
+  echo -e "\n[+1/2] Deploying and initializing example app..."
+  EXAMPLE_APP_PUBLISH_OUTPUT=$($(echo worm sui deploy "$EXAMPLE_APP_PATH" -n "$NETWORK" "$PRIVATE_KEY_ARG"))
+  EXAMPLE_APP_PACKAGE_ID=$(echo "$EXAMPLE_APP_PUBLISH_OUTPUT" | grep -oP 'Published to +\K.*')
+  echo "$EXAMPLE_APP_PUBLISH_OUTPUT"
+
+  EXAMPLE_INIT_OUTPUT=$($(echo worm sui init-example-message-app -n "$NETWORK" -p "$EXAMPLE_APP_PACKAGE_ID" -w "$WORMHOLE_STATE_OBJECT_ID" "$PRIVATE_KEY_ARG"))
+  EXAMPLE_APP_STATE_OBJECT_ID=$(echo "$EXAMPLE_INIT_OUTPUT" | grep -oP 'Example app state object ID +\K.*')
+  echo "$EXAMPLE_INIT_OUTPUT"
+
+  echo -e "\n[+2/2] Deploying example coins..."
+  EXAMPLE_COIN_PUBLISH_OUTPUT=$($(echo worm sui deploy "$EXAMPLE_COIN_PATH" -n "$NETWORK" "$PRIVATE_KEY_ARG"))
+  echo "$EXAMPLE_COIN_PUBLISH_OUTPUT"
+
+  echo -e "\nWormhole package ID: $WORMHOLE_PACKAGE_ID"
+  echo "Token bridge package ID: $TOKEN_BRIDGE_PACKAGE_ID"
+  echo "Wormhole state object ID: $WORMHOLE_STATE_OBJECT_ID"
+  echo "Token bridge state object ID: $TOKEN_BRIDGE_STATE_OBJECT_ID"
+
+  echo -e "\nPublish message command:" worm sui publish-example-message -n devnet -p "$EXAMPLE_APP_PACKAGE_ID" -s "$EXAMPLE_APP_STATE_OBJECT_ID" -w "$WORMHOLE_STATE_OBJECT_ID" -m "hello" "$PRIVATE_KEY_ARG"
+fi
+
+echo -e "\nDeployments successful!"

+ 11 - 0
target_chains/sui/vendor/wormhole_movement_testnet/scripts/node_builder.sh

@@ -0,0 +1,11 @@
+#!/bin/bash
+
+git clone https://github.com/MystenLabs/sui.git --branch devnet
+cd sui
+# Corresponds to https://github.com/MystenLabs/sui/releases/tag/mainnet-v1.19.1
+git reset --hard 041c5f2bae2fe52079e44b70514333532d69f4e6
+
+cargo --locked install --path crates/sui
+cargo --locked install --path crates/sui-faucet
+cargo --locked install --path crates/sui-gateway
+cargo --locked install --path crates/sui-node

+ 22 - 0
target_chains/sui/vendor/wormhole_movement_testnet/scripts/register_devnet.sh

@@ -0,0 +1,22 @@
+#!/bin/bash
+
+set -e
+
+DOTENV=$(realpath "$(dirname "$0")"/../.env)
+[ -f $DOTENV ] || (echo "$DOTENV does not exist." >&2; exit 1)
+
+# 1. load variables from .env file
+. $DOTENV
+
+# 2. next we get all the token bridge registration VAAs from the environment
+# if a new VAA is added, this will automatically pick it up
+VAAS=$(set | grep "REGISTER_.*_TOKEN_BRIDGE_VAA" | grep -v SUI | cut -d '=' -f1)
+
+# 3. use 'worm' to submit each registration VAA
+for VAA in $VAAS
+do
+    VAA=${!VAA}
+    worm submit $VAA --chain sui --network devnet
+done
+
+echo "Registrations successful."

+ 3 - 0
target_chains/sui/vendor/wormhole_movement_testnet/scripts/setup_rust.sh

@@ -0,0 +1,3 @@
+#!/bin/bash
+
+curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y

+ 5 - 0
target_chains/sui/vendor/wormhole_movement_testnet/scripts/start_node.sh

@@ -0,0 +1,5 @@
+#!/bin/bash
+
+set -x
+
+sui start 2>&1

+ 31 - 0
target_chains/sui/vendor/wormhole_movement_testnet/scripts/switch.sh

@@ -0,0 +1,31 @@
+#!/bin/bash
+
+network="$1"
+valid_networks=("devnet" "testnet" "mainnet" "reset")
+
+usage() {
+    echo "Usage: $0 {devnet|testnet|mainnet|reset}" >&2
+    exit 1
+}
+
+if [[ ! " ${valid_networks[@]} " =~ " ${network} " ]]; then
+    echo "Error: Unrecognized network '${network}'."
+    usage
+fi
+
+git ls-files | grep 'Move.toml' | while read -r file; do
+    if [[ "$network" == "reset" ]]; then
+        echo "Resetting $file"
+        git checkout "$file" --quiet
+    else
+        dir=$(dirname "$file")
+        base=$(basename "$file")
+        new_file="${dir}/Move.$network.toml"
+        if [ -f "$new_file" ]; then
+            echo "Switching $file to $new_file"
+            rm "$file"
+            # Create a relative symlink
+            (cd "$dir" && ln -s "$(basename "$new_file")" "$base")
+        fi
+    fi
+done

+ 6 - 0
target_chains/sui/vendor/wormhole_movement_testnet/scripts/wait_for_devnet.sh

@@ -0,0 +1,6 @@
+#!/bin/bash
+
+set -e
+
+# Wait for sui to start
+while [[ "$(curl -X POST -H "Content-Type: application/json" -d '{ "jsonrpc":"2.0", "method":"rpc.discover","id":1 }' -s -o /dev/null -w '%{http_code}' 0.0.0.0:9000/)" != "200" ]]; do sleep 1; done

+ 4 - 0
target_chains/sui/vendor/wormhole_movement_testnet/testing/.gitignore

@@ -0,0 +1,4 @@
+node_modules
+sui.log.*
+./token_bridge/
+./wormhole/

+ 13 - 0
target_chains/sui/vendor/wormhole_movement_testnet/testing/Makefile

@@ -0,0 +1,13 @@
+-include ../Makefile.help
+
+.PHONY: clean
+clean:
+	rm -rf node_modules
+
+node_modules:
+	npm ci
+
+.PHONY: test
+## Run tests
+test: node_modules
+	bash run_integration_test.sh

+ 78 - 0
target_chains/sui/vendor/wormhole_movement_testnet/testing/js/00_environment.ts

@@ -0,0 +1,78 @@
+import { expect } from "chai";
+import * as mock from "@certusone/wormhole-sdk/lib/cjs/mock";
+
+import {
+  CREATOR_PRIVATE_KEY,
+  GUARDIAN_PRIVATE_KEY,
+  RELAYER_PRIVATE_KEY,
+  WALLET_PRIVATE_KEY,
+} from "./helpers/consts";
+import {
+  Ed25519Keypair,
+  JsonRpcProvider,
+  localnetConnection,
+  RawSigner,
+} from "@mysten/sui.js";
+
+describe(" 0. Environment", () => {
+  const provider = new JsonRpcProvider(localnetConnection);
+
+  // User wallet.
+  const wallet = new RawSigner(
+    Ed25519Keypair.fromSecretKey(WALLET_PRIVATE_KEY),
+    provider
+  );
+
+  // Relayer wallet.
+  const relayer = new RawSigner(
+    Ed25519Keypair.fromSecretKey(RELAYER_PRIVATE_KEY),
+    provider
+  );
+
+  // Deployer wallet.
+  const creator = new RawSigner(
+    Ed25519Keypair.fromSecretKey(CREATOR_PRIVATE_KEY),
+    provider
+  );
+
+  describe("Verify Local Validator", () => {
+    it("Balance", async () => {
+      // Balance check wallet.
+      {
+        const coinData = await wallet
+          .getAddress()
+          .then((owner) =>
+            provider
+              .getCoins({ owner, coinType: "0x2::sui::SUI" })
+              .then((result) => result.data)
+          );
+        expect(coinData).has.length(5);
+      }
+
+      // Balance check relayer.
+      {
+        const coinData = await relayer
+          .getAddress()
+          .then((owner) =>
+            provider
+              .getCoins({ owner, coinType: "0x2::sui::SUI" })
+              .then((result) => result.data)
+          );
+        expect(coinData).has.length(5);
+      }
+
+      // Balance check creator. This should only have one gas object at this
+      // point.
+      {
+        const coinData = await creator
+          .getAddress()
+          .then((owner) =>
+            provider
+              .getCoins({ owner, coinType: "0x2::sui::SUI" })
+              .then((result) => result.data)
+          );
+        expect(coinData).has.length(1);
+      }
+    });
+  });
+});

+ 109 - 0
target_chains/sui/vendor/wormhole_movement_testnet/testing/js/01_wormhole.ts

@@ -0,0 +1,109 @@
+import { expect } from "chai";
+
+import { WALLET_PRIVATE_KEY, WORMHOLE_STATE_ID } from "./helpers/consts";
+import {
+  Ed25519Keypair,
+  JsonRpcProvider,
+  localnetConnection,
+  RawSigner,
+  SUI_CLOCK_OBJECT_ID,
+  TransactionBlock,
+} from "@mysten/sui.js";
+import { getPackageId } from "./helpers/utils";
+import { addPrepareMessageAndPublishMessage } from "./helpers/wormhole/testPublishMessage";
+
+describe(" 1. Wormhole", () => {
+  const provider = new JsonRpcProvider(localnetConnection);
+
+  // User wallet.
+  const wallet = new RawSigner(
+    Ed25519Keypair.fromSecretKey(WALLET_PRIVATE_KEY),
+    provider
+  );
+
+  describe("Publish Message", () => {
+    it("Check `WormholeMessage` Event", async () => {
+      const wormholePackage = await getPackageId(
+        wallet.provider,
+        WORMHOLE_STATE_ID
+      );
+
+      const owner = await wallet.getAddress();
+
+      // Create emitter cap.
+      const emitterCapId = await (async () => {
+        const tx = new TransactionBlock();
+        const [emitterCap] = tx.moveCall({
+          target: `${wormholePackage}::emitter::new`,
+          arguments: [tx.object(WORMHOLE_STATE_ID)],
+        });
+        tx.transferObjects([emitterCap], tx.pure(owner));
+
+        // Execute and fetch created Emitter cap.
+        return wallet
+          .signAndExecuteTransactionBlock({
+            transactionBlock: tx,
+            options: {
+              showObjectChanges: true,
+            },
+          })
+          .then((result) => {
+            const found = result.objectChanges?.filter(
+              (item) => "created" === item.type!
+            );
+            if (found?.length == 1 && "objectId" in found[0]) {
+              return found[0].objectId;
+            }
+
+            throw new Error("no objects found");
+          });
+      })();
+
+      // Publish messages using emitter cap.
+      {
+        const nonce = 69;
+        const basePayload = "All your base are belong to us.";
+
+        const numMessages = 32;
+        const payloads: string[] = [];
+        const tx = new TransactionBlock();
+
+        // Construct transaction block to send multiple messages.
+        for (let i = 0; i < numMessages; ++i) {
+          // Make a unique message.
+          const payload = basePayload + `... ${i}`;
+          payloads.push(payload);
+
+          addPrepareMessageAndPublishMessage(
+            tx,
+            wormholePackage,
+            WORMHOLE_STATE_ID,
+            emitterCapId,
+            nonce,
+            payload
+          );
+        }
+
+        const events = await wallet
+          .signAndExecuteTransactionBlock({
+            transactionBlock: tx,
+            options: {
+              showEvents: true,
+            },
+          })
+          .then((result) => result.events!);
+        expect(events).has.length(numMessages);
+
+        for (let i = 0; i < numMessages; ++i) {
+          const eventData = events[i].parsedJson!;
+          expect(eventData.consistency_level).equals(0);
+          expect(eventData.nonce).equals(nonce);
+          expect(eventData.payload).deep.equals([...Buffer.from(payloads[i])]);
+          expect(eventData.sender).equals(emitterCapId);
+          expect(eventData.sequence).equals(i.toString());
+          expect(BigInt(eventData.timestamp) > 0n).is.true;
+        }
+      }
+    });
+  });
+});

+ 32 - 0
target_chains/sui/vendor/wormhole_movement_testnet/testing/js/helpers/build.ts

@@ -0,0 +1,32 @@
+import { fromB64, normalizeSuiObjectId } from "@mysten/sui.js";
+import { execSync, ExecSyncOptionsWithStringEncoding } from "child_process";
+import { UTF8 } from "./consts";
+
+export const EXEC_UTF8: ExecSyncOptionsWithStringEncoding = { encoding: UTF8 };
+
+export function buildForBytecode(packagePath: string) {
+  const buildOutput: {
+    modules: string[];
+    dependencies: string[];
+  } = JSON.parse(
+    execSync(
+      `sui move build --dump-bytecode-as-base64 -p ${packagePath} 2> /dev/null`,
+      EXEC_UTF8
+    )
+  );
+  return {
+    modules: buildOutput.modules.map((m: string) => Array.from(fromB64(m))),
+    dependencies: buildOutput.dependencies.map((d: string) =>
+      normalizeSuiObjectId(d)
+    ),
+  };
+}
+
+export function buildForDigest(packagePath: string) {
+  const digest = execSync(
+    `sui move build --dump-package-digest -p ${packagePath} 2> /dev/null`,
+    EXEC_UTF8
+  ).substring(0, 64);
+
+  return Buffer.from(digest, "hex");
+}

+ 40 - 0
target_chains/sui/vendor/wormhole_movement_testnet/testing/js/helpers/consts.ts

@@ -0,0 +1,40 @@
+// NOTE: modify these to reflect current versions of packages
+export const VERSION_WORMHOLE = 1;
+export const VERSION_TOKEN_BRIDGE = 1;
+
+// keystore
+export const KEYSTORE = [
+  "AB522qKKEsXMTFRD2SG3Het/02S/ZBOugmcH3R1CDG6l",
+  "AOmPq9B16F3W3ijO/4s9hI6v8LdiYCawKAW31PKpg4Qp",
+  "AGA20wtGcwbcNAG4nwapbQ5wIuXwkYQEWFUoSVAxctHb",
+];
+
+// wallets
+export const WALLET_PRIVATE_KEY = Buffer.from(KEYSTORE[0], "base64").subarray(
+  1
+);
+export const RELAYER_PRIVATE_KEY = Buffer.from(KEYSTORE[1], "base64").subarray(
+  1
+);
+export const CREATOR_PRIVATE_KEY = Buffer.from(KEYSTORE[2], "base64").subarray(
+  1
+);
+
+// guardian signer
+export const GUARDIAN_PRIVATE_KEY =
+  "cfb12303a19cde580bb4dd771639b0d26bc68353645571a8cff516ab2ee113a0";
+
+// wormhole
+export const WORMHOLE_STATE_ID =
+  "0xc561a02a143575e53b87ba6c1476f053a307eac5179cb1c8121a3d3b220b81c1";
+
+// token bridge
+export const TOKEN_BRIDGE_STATE_ID =
+  "0x1c8de839f6331f2d745eb53b1b595bc466b4001c11617b0b66214b2e25ee72fc";
+
+// governance
+export const GOVERNANCE_EMITTER =
+  "0000000000000000000000000000000000000000000000000000000000000004";
+
+// file encoding
+export const UTF8: BufferEncoding = "utf-8";

+ 42 - 0
target_chains/sui/vendor/wormhole_movement_testnet/testing/js/helpers/error/moveAbort.ts

@@ -0,0 +1,42 @@
+export function parseMoveAbort(errorMessage: string) {
+  const parsed = errorMessage.matchAll(
+    /MoveAbort\(MoveLocation { module: ModuleId { address: ([0-9a-f]{64}), name: Identifier\("([A-Za-z_]+)"\) }, function: ([0-9]+), instruction: ([0-9]+), function_name: Some\("([A-Za-z_]+)"\) }, ([0-9]+)\) in command ([0-9]+)/g
+  );
+
+  return parsed.next().value.slice(1, 8);
+}
+
+export class MoveAbort {
+  packageId: string;
+  moduleName: string;
+  functionName: string;
+  errorCode: bigint;
+  command: number;
+
+  constructor(
+    packageId: string,
+    moduleName: string,
+    functionName: string,
+    errorCode: string,
+    command: string
+  ) {
+    this.packageId = packageId;
+    this.moduleName = moduleName;
+    this.functionName = functionName;
+    this.errorCode = BigInt(errorCode);
+    this.command = Number(command);
+  }
+
+  static parseError(errorMessage: string): MoveAbort {
+    const [packageId, moduleName, , , functionName, errorCode, command] =
+      parseMoveAbort(errorMessage);
+
+    return new MoveAbort(
+      "0x" + packageId,
+      moduleName,
+      functionName,
+      errorCode,
+      command
+    );
+  }
+}

+ 22 - 0
target_chains/sui/vendor/wormhole_movement_testnet/testing/js/helpers/error/wormhole.ts

@@ -0,0 +1,22 @@
+import { MoveAbort } from "./moveAbort";
+
+export function parseWormholeError(errorMessage: string) {
+  const abort = MoveAbort.parseError(errorMessage);
+  const code = abort.errorCode;
+
+  switch (abort.moduleName) {
+    case "required_version": {
+      switch (code) {
+        case 0n: {
+          return "E_OUTDATED_VERSION";
+        }
+        default: {
+          throw new Error(`unrecognized error code: ${abort}`);
+        }
+      }
+    }
+    default: {
+      throw new Error(`unrecognized module: ${abort}`);
+    }
+  }
+}

+ 75 - 0
target_chains/sui/vendor/wormhole_movement_testnet/testing/js/helpers/setup.ts

@@ -0,0 +1,75 @@
+import * as fs from "fs";
+import * as mock from "@certusone/wormhole-sdk/lib/cjs/mock";
+import { GUARDIAN_PRIVATE_KEY, UTF8 } from "./consts";
+
+export function generateVaaFromDigest(
+  digest: Buffer,
+  governance: mock.GovernanceEmitter
+) {
+  const timestamp = 12345678;
+  const published = governance.publishWormholeUpgradeContract(
+    timestamp,
+    2,
+    "0x" + digest.toString("hex")
+  );
+
+  // Sui is not supported yet by the SDK, so we need to adjust the payload.
+  published.writeUInt16BE(21, published.length - 34);
+
+  // We will use the signed VAA when we execute the upgrade.
+  const guardians = new mock.MockGuardians(0, [GUARDIAN_PRIVATE_KEY]);
+  return guardians.addSignatures(published, [0]);
+}
+
+export function modifyHardCodedVersionControl(
+  packagePath: string,
+  currentVersion: number,
+  newVersion: number
+) {
+  const versionControlDotMove = `${packagePath}/sources/version_control.move`;
+
+  const contents = fs.readFileSync(versionControlDotMove, UTF8);
+  const src = `const CURRENT_BUILD_VERSION: u64 = ${currentVersion}`;
+  if (contents.indexOf(src) < 0) {
+    throw new Error("current version not found");
+  }
+
+  const dst = `const CURRENT_BUILD_VERSION: u64 = ${newVersion}`;
+  fs.writeFileSync(versionControlDotMove, contents.replace(src, dst), UTF8);
+}
+
+export function setUpWormholeDirectory(
+  srcWormholePath: string,
+  dstWormholePath: string
+) {
+  fs.cpSync(srcWormholePath, dstWormholePath, { recursive: true });
+
+  // Remove irrelevant files. This part is not necessary, but is helpful
+  // for debugging a clean package directory.
+  const removeThese = [
+    "Move.devnet.toml",
+    "Move.lock",
+    "Makefile",
+    "README.md",
+    "build",
+  ];
+  for (const basename of removeThese) {
+    fs.rmSync(`${dstWormholePath}/${basename}`, {
+      recursive: true,
+      force: true,
+    });
+  }
+
+  // Fix Move.toml file.
+  const moveTomlPath = `${dstWormholePath}/Move.toml`;
+  const moveToml = fs.readFileSync(moveTomlPath, UTF8);
+  fs.writeFileSync(
+    moveTomlPath,
+    moveToml.replace(`wormhole = "_"`, `wormhole = "0x0"`),
+    UTF8
+  );
+}
+
+export function cleanUpPackageDirectory(packagePath: string) {
+  fs.rmSync(packagePath, { recursive: true, force: true });
+}

+ 73 - 0
target_chains/sui/vendor/wormhole_movement_testnet/testing/js/helpers/upgrade.ts

@@ -0,0 +1,73 @@
+import {
+  RawSigner,
+  SUI_CLOCK_OBJECT_ID,
+  TransactionBlock,
+} from "@mysten/sui.js";
+import { buildForBytecode } from "./build";
+import { getPackageId } from "./utils";
+
+export async function buildAndUpgradeWormhole(
+  signer: RawSigner,
+  signedVaa: Buffer,
+  wormholePath: string,
+  wormholeStateId: string
+) {
+  const wormholePackage = await getPackageId(signer.provider, wormholeStateId);
+
+  const tx = new TransactionBlock();
+
+  // Authorize upgrade.
+  const [upgradeTicket] = tx.moveCall({
+    target: `${wormholePackage}::upgrade_contract::authorize_upgrade`,
+    arguments: [
+      tx.object(wormholeStateId),
+      tx.pure(Array.from(signedVaa)),
+      tx.object(SUI_CLOCK_OBJECT_ID),
+    ],
+  });
+
+  // Build and generate modules and dependencies for upgrade.
+  const { modules, dependencies } = buildForBytecode(wormholePath);
+  const [upgradeReceipt] = tx.upgrade({
+    modules,
+    dependencies,
+    packageId: wormholePackage,
+    ticket: upgradeTicket,
+  });
+
+  // Commit upgrade.
+  tx.moveCall({
+    target: `${wormholePackage}::upgrade_contract::commit_upgrade`,
+    arguments: [tx.object(wormholeStateId), upgradeReceipt],
+  });
+
+  // Cannot auto compute gas budget, so we need to configure it manually.
+  // Gas ~215m.
+  tx.setGasBudget(215_000_000n);
+
+  return signer.signAndExecuteTransactionBlock({
+    transactionBlock: tx,
+    options: {
+      showEffects: true,
+      showEvents: true,
+    },
+  });
+}
+
+export async function migrate(signer: RawSigner, stateId: string) {
+  const contractPackage = await getPackageId(signer.provider, stateId);
+
+  const tx = new TransactionBlock();
+  tx.moveCall({
+    target: `${contractPackage}::migrate::migrate`,
+    arguments: [tx.object(stateId)],
+  });
+
+  return signer.signAndExecuteTransactionBlock({
+    transactionBlock: tx,
+    options: {
+      showEffects: true,
+      showEvents: true,
+    },
+  });
+}

+ 27 - 0
target_chains/sui/vendor/wormhole_movement_testnet/testing/js/helpers/utils.ts

@@ -0,0 +1,27 @@
+import { JsonRpcProvider } from "@mysten/sui.js";
+
+export async function getPackageId(
+  provider: JsonRpcProvider,
+  stateId: string
+): Promise<string> {
+  const state = await provider
+    .getObject({
+      id: stateId,
+      options: {
+        showContent: true,
+      },
+    })
+    .then((result) => {
+      if (result.data?.content?.dataType == "moveObject") {
+        return result.data.content.fields;
+      }
+
+      throw new Error("not move object");
+    });
+
+  if ("upgrade_cap" in state) {
+    return state.upgrade_cap.fields.package;
+  }
+
+  throw new Error("upgrade_cap not found");
+}

+ 31 - 0
target_chains/sui/vendor/wormhole_movement_testnet/testing/js/helpers/wormhole/testPublishMessage.ts

@@ -0,0 +1,31 @@
+import { SUI_CLOCK_OBJECT_ID, TransactionBlock } from "@mysten/sui.js";
+
+export function addPrepareMessageAndPublishMessage(
+  tx: TransactionBlock,
+  wormholePackage: string,
+  wormholeStateId: string,
+  emitterCapId: string,
+  nonce: number,
+  payload: number[] | string
+): TransactionBlock {
+  const [feeAmount] = tx.moveCall({
+    target: `${wormholePackage}::state::message_fee`,
+    arguments: [tx.object(wormholeStateId)],
+  });
+  const [wormholeFee] = tx.splitCoins(tx.gas, [feeAmount]);
+  const [messageTicket] = tx.moveCall({
+    target: `${wormholePackage}::publish_message::prepare_message`,
+    arguments: [tx.object(emitterCapId), tx.pure(nonce), tx.pure(payload)],
+  });
+  tx.moveCall({
+    target: `${wormholePackage}::publish_message::publish_message`,
+    arguments: [
+      tx.object(wormholeStateId),
+      wormholeFee,
+      messageTicket,
+      tx.object(SUI_CLOCK_OBJECT_ID),
+    ],
+  });
+
+  return tx;
+}

+ 5917 - 0
target_chains/sui/vendor/wormhole_movement_testnet/testing/package-lock.json

@@ -0,0 +1,5917 @@
+{
+  "name": "wormhole-sui-integration-test",
+  "version": "1.0.0",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "wormhole-sui-integration-test",
+      "version": "1.0.0",
+      "license": "MIT",
+      "dependencies": {
+        "@certusone/wormhole-sdk": "^0.9.12",
+        "@mysten/sui.js": "^0.32.2",
+        "chai": "^4.3.7",
+        "mocha": "^10.2.0",
+        "prettier": "^2.8.7",
+        "ts-mocha": "^10.0.0",
+        "ts-node": "^10.9.1",
+        "typescript": "^5.0.4"
+      },
+      "devDependencies": {
+        "@types/chai": "^4.3.4",
+        "@types/mocha": "^10.0.1",
+        "@types/node": "^18.15.11"
+      }
+    },
+    "node_modules/@apollo/client": {
+      "version": "3.7.11",
+      "resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.7.11.tgz",
+      "integrity": "sha512-uLg2KtxoAyj9ta7abLxXx8cGRM7HypCkXVmxtL7Ko//N5g37aoJ3ca7VYoFCMUFO1BXBulj+yKVl0U3+ILj5AQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@graphql-typed-document-node/core": "^3.1.1",
+        "@wry/context": "^0.7.0",
+        "@wry/equality": "^0.5.0",
+        "@wry/trie": "^0.3.0",
+        "graphql-tag": "^2.12.6",
+        "hoist-non-react-statics": "^3.3.2",
+        "optimism": "^0.16.2",
+        "prop-types": "^15.7.2",
+        "response-iterator": "^0.2.6",
+        "symbol-observable": "^4.0.0",
+        "ts-invariant": "^0.10.3",
+        "tslib": "^2.3.0",
+        "zen-observable-ts": "^1.2.5"
+      },
+      "peerDependencies": {
+        "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0",
+        "graphql-ws": "^5.5.5",
+        "react": "^16.8.0 || ^17.0.0 || ^18.0.0",
+        "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0",
+        "subscriptions-transport-ws": "^0.9.0 || ^0.11.0"
+      },
+      "peerDependenciesMeta": {
+        "graphql-ws": {
+          "optional": true
+        },
+        "react": {
+          "optional": true
+        },
+        "react-dom": {
+          "optional": true
+        },
+        "subscriptions-transport-ws": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@babel/runtime": {
+      "version": "7.21.0",
+      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz",
+      "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==",
+      "license": "MIT",
+      "dependencies": {
+        "regenerator-runtime": "^0.13.11"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@certusone/wormhole-sdk": {
+      "version": "0.9.12",
+      "resolved": "https://registry.npmjs.org/@certusone/wormhole-sdk/-/wormhole-sdk-0.9.12.tgz",
+      "integrity": "sha512-ywMNc/tHg6qb9dcZLND1BMUISp7eFN+ksymOgjhwQcZZ/KUA/N1uVvbMVs0uSx+i0y4VloO9MwGc/uFnYKNsMQ==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@certusone/wormhole-sdk-proto-web": "0.0.6",
+        "@certusone/wormhole-sdk-wasm": "^0.0.1",
+        "@coral-xyz/borsh": "0.2.6",
+        "@injectivelabs/networks": "^1.0.73",
+        "@injectivelabs/sdk-ts": "^1.0.368",
+        "@injectivelabs/utils": "^1.0.63",
+        "@project-serum/anchor": "^0.25.0",
+        "@solana/spl-token": "^0.3.5",
+        "@solana/web3.js": "^1.66.2",
+        "@terra-money/terra.js": "^3.1.3",
+        "@xpla/xpla.js": "^0.2.1",
+        "algosdk": "^1.15.0",
+        "aptos": "1.5.0",
+        "axios": "^0.24.0",
+        "bech32": "^2.0.0",
+        "binary-parser": "^2.2.1",
+        "bs58": "^4.0.1",
+        "elliptic": "^6.5.4",
+        "js-base64": "^3.6.1",
+        "near-api-js": "^1.0.0"
+      }
+    },
+    "node_modules/@certusone/wormhole-sdk-proto-web": {
+      "version": "0.0.6",
+      "resolved": "https://registry.npmjs.org/@certusone/wormhole-sdk-proto-web/-/wormhole-sdk-proto-web-0.0.6.tgz",
+      "integrity": "sha512-LTyjsrWryefx5WmkoBP6FQ2EjLxhMExAGxLkloHUhufVQZdrbGh0htBBUviP+HaDSJBCMPMtulNFwkBJV6muqQ==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@improbable-eng/grpc-web": "^0.15.0",
+        "protobufjs": "^7.0.0",
+        "rxjs": "^7.5.6"
+      }
+    },
+    "node_modules/@certusone/wormhole-sdk-proto-web/node_modules/@improbable-eng/grpc-web": {
+      "version": "0.15.0",
+      "resolved": "https://registry.npmjs.org/@improbable-eng/grpc-web/-/grpc-web-0.15.0.tgz",
+      "integrity": "sha512-ERft9/0/8CmYalqOVnJnpdDry28q+j+nAlFFARdjyxXDJ+Mhgv9+F600QC8BR9ygOfrXRlAk6CvST2j+JCpQPg==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "browser-headers": "^0.4.1"
+      },
+      "peerDependencies": {
+        "google-protobuf": "^3.14.0"
+      }
+    },
+    "node_modules/@certusone/wormhole-sdk-proto-web/node_modules/long": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz",
+      "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==",
+      "license": "Apache-2.0"
+    },
+    "node_modules/@certusone/wormhole-sdk-proto-web/node_modules/protobufjs": {
+      "version": "7.2.3",
+      "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.3.tgz",
+      "integrity": "sha512-TtpvOqwB5Gdz/PQmOjgsrGH1nHjAQVCN7JG4A6r1sXRWESL5rNMAiRcBQlCAdKxZcAbstExQePYG8xof/JVRgg==",
+      "hasInstallScript": true,
+      "license": "BSD-3-Clause",
+      "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/node": ">=13.7.0",
+        "long": "^5.0.0"
+      },
+      "engines": {
+        "node": ">=12.0.0"
+      }
+    },
+    "node_modules/@certusone/wormhole-sdk-wasm": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/@certusone/wormhole-sdk-wasm/-/wormhole-sdk-wasm-0.0.1.tgz",
+      "integrity": "sha512-LdIwLhOyr4pPs2jqYubqC7d4UkqYBX0EG/ppspQlW3qlVE0LZRMrH6oVzzLMyHtV0Rw7O9sIKzORW/T3mrJv2w==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@types/long": "^4.0.2",
+        "@types/node": "^18.0.3"
+      }
+    },
+    "node_modules/@certusone/wormhole-sdk/node_modules/axios": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz",
+      "integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==",
+      "license": "MIT",
+      "dependencies": {
+        "follow-redirects": "^1.14.4"
+      }
+    },
+    "node_modules/@classic-terra/terra.proto": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@classic-terra/terra.proto/-/terra.proto-1.1.0.tgz",
+      "integrity": "sha512-bYhQG5LUaGF0KPRY9hYT/HEcd1QExZPQd6zLV/rQkCe/eDxfwFRLzZHpaaAdfWoAAZjsRWqJbUCqCg7gXBbJpw==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@improbable-eng/grpc-web": "^0.14.1",
+        "google-protobuf": "^3.17.3",
+        "long": "^4.0.0",
+        "protobufjs": "~6.11.2"
+      }
+    },
+    "node_modules/@confio/ics23": {
+      "version": "0.6.8",
+      "resolved": "https://registry.npmjs.org/@confio/ics23/-/ics23-0.6.8.tgz",
+      "integrity": "sha512-wB6uo+3A50m0sW/EWcU64xpV/8wShZ6bMTa7pF8eYsTrSkQA7oLUIJcs/wb8g4y2Oyq701BaGiO6n/ak5WXO1w==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@noble/hashes": "^1.0.0",
+        "protobufjs": "^6.8.8"
+      }
+    },
+    "node_modules/@coral-xyz/borsh": {
+      "version": "0.2.6",
+      "resolved": "https://registry.npmjs.org/@coral-xyz/borsh/-/borsh-0.2.6.tgz",
+      "integrity": "sha512-y6nmHw1bFcJib7sMHsQPpC8r47xhqDZVvhUdna7NUPzpSbOZG6f46N21+aXsQ2w/tG8Ggls488J/ZmwbgVmyjg==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "bn.js": "^5.1.2",
+        "buffer-layout": "^1.2.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "peerDependencies": {
+        "@solana/web3.js": "^1.2.0"
+      }
+    },
+    "node_modules/@cosmjs/amino": {
+      "version": "0.30.1",
+      "resolved": "https://registry.npmjs.org/@cosmjs/amino/-/amino-0.30.1.tgz",
+      "integrity": "sha512-yNHnzmvAlkETDYIpeCTdVqgvrdt1qgkOXwuRVi8s27UKI5hfqyE9fJ/fuunXE6ZZPnKkjIecDznmuUOMrMvw4w==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@cosmjs/crypto": "^0.30.1",
+        "@cosmjs/encoding": "^0.30.1",
+        "@cosmjs/math": "^0.30.1",
+        "@cosmjs/utils": "^0.30.1"
+      }
+    },
+    "node_modules/@cosmjs/crypto": {
+      "version": "0.30.1",
+      "resolved": "https://registry.npmjs.org/@cosmjs/crypto/-/crypto-0.30.1.tgz",
+      "integrity": "sha512-rAljUlake3MSXs9xAm87mu34GfBLN0h/1uPPV6jEwClWjNkAMotzjC0ab9MARy5FFAvYHL3lWb57bhkbt2GtzQ==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@cosmjs/encoding": "^0.30.1",
+        "@cosmjs/math": "^0.30.1",
+        "@cosmjs/utils": "^0.30.1",
+        "@noble/hashes": "^1",
+        "bn.js": "^5.2.0",
+        "elliptic": "^6.5.4",
+        "libsodium-wrappers": "^0.7.6"
+      }
+    },
+    "node_modules/@cosmjs/encoding": {
+      "version": "0.30.1",
+      "resolved": "https://registry.npmjs.org/@cosmjs/encoding/-/encoding-0.30.1.tgz",
+      "integrity": "sha512-rXmrTbgqwihORwJ3xYhIgQFfMSrwLu1s43RIK9I8EBudPx3KmnmyAKzMOVsRDo9edLFNuZ9GIvysUCwQfq3WlQ==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "base64-js": "^1.3.0",
+        "bech32": "^1.1.4",
+        "readonly-date": "^1.0.0"
+      }
+    },
+    "node_modules/@cosmjs/encoding/node_modules/bech32": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz",
+      "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==",
+      "license": "MIT"
+    },
+    "node_modules/@cosmjs/json-rpc": {
+      "version": "0.30.1",
+      "resolved": "https://registry.npmjs.org/@cosmjs/json-rpc/-/json-rpc-0.30.1.tgz",
+      "integrity": "sha512-pitfC/2YN9t+kXZCbNuyrZ6M8abnCC2n62m+JtU9vQUfaEtVsgy+1Fk4TRQ175+pIWSdBMFi2wT8FWVEE4RhxQ==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@cosmjs/stream": "^0.30.1",
+        "xstream": "^11.14.0"
+      }
+    },
+    "node_modules/@cosmjs/math": {
+      "version": "0.30.1",
+      "resolved": "https://registry.npmjs.org/@cosmjs/math/-/math-0.30.1.tgz",
+      "integrity": "sha512-yaoeI23pin9ZiPHIisa6qqLngfnBR/25tSaWpkTm8Cy10MX70UF5oN4+/t1heLaM6SSmRrhk3psRkV4+7mH51Q==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "bn.js": "^5.2.0"
+      }
+    },
+    "node_modules/@cosmjs/proto-signing": {
+      "version": "0.30.1",
+      "resolved": "https://registry.npmjs.org/@cosmjs/proto-signing/-/proto-signing-0.30.1.tgz",
+      "integrity": "sha512-tXh8pPYXV4aiJVhTKHGyeZekjj+K9s2KKojMB93Gcob2DxUjfKapFYBMJSgfKPuWUPEmyr8Q9km2hplI38ILgQ==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@cosmjs/amino": "^0.30.1",
+        "@cosmjs/crypto": "^0.30.1",
+        "@cosmjs/encoding": "^0.30.1",
+        "@cosmjs/math": "^0.30.1",
+        "@cosmjs/utils": "^0.30.1",
+        "cosmjs-types": "^0.7.1",
+        "long": "^4.0.0"
+      }
+    },
+    "node_modules/@cosmjs/socket": {
+      "version": "0.30.1",
+      "resolved": "https://registry.npmjs.org/@cosmjs/socket/-/socket-0.30.1.tgz",
+      "integrity": "sha512-r6MpDL+9N+qOS/D5VaxnPaMJ3flwQ36G+vPvYJsXArj93BjgyFB7BwWwXCQDzZ+23cfChPUfhbINOenr8N2Kow==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@cosmjs/stream": "^0.30.1",
+        "isomorphic-ws": "^4.0.1",
+        "ws": "^7",
+        "xstream": "^11.14.0"
+      }
+    },
+    "node_modules/@cosmjs/stargate": {
+      "version": "0.30.1",
+      "resolved": "https://registry.npmjs.org/@cosmjs/stargate/-/stargate-0.30.1.tgz",
+      "integrity": "sha512-RdbYKZCGOH8gWebO7r6WvNnQMxHrNXInY/gPHPzMjbQF6UatA6fNM2G2tdgS5j5u7FTqlCI10stNXrknaNdzog==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@confio/ics23": "^0.6.8",
+        "@cosmjs/amino": "^0.30.1",
+        "@cosmjs/encoding": "^0.30.1",
+        "@cosmjs/math": "^0.30.1",
+        "@cosmjs/proto-signing": "^0.30.1",
+        "@cosmjs/stream": "^0.30.1",
+        "@cosmjs/tendermint-rpc": "^0.30.1",
+        "@cosmjs/utils": "^0.30.1",
+        "cosmjs-types": "^0.7.1",
+        "long": "^4.0.0",
+        "protobufjs": "~6.11.3",
+        "xstream": "^11.14.0"
+      }
+    },
+    "node_modules/@cosmjs/stream": {
+      "version": "0.30.1",
+      "resolved": "https://registry.npmjs.org/@cosmjs/stream/-/stream-0.30.1.tgz",
+      "integrity": "sha512-Fg0pWz1zXQdoxQZpdHRMGvUH5RqS6tPv+j9Eh7Q953UjMlrwZVo0YFLC8OTf/HKVf10E4i0u6aM8D69Q6cNkgQ==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "xstream": "^11.14.0"
+      }
+    },
+    "node_modules/@cosmjs/tendermint-rpc": {
+      "version": "0.30.1",
+      "resolved": "https://registry.npmjs.org/@cosmjs/tendermint-rpc/-/tendermint-rpc-0.30.1.tgz",
+      "integrity": "sha512-Z3nCwhXSbPZJ++v85zHObeUggrEHVfm1u18ZRwXxFE9ZMl5mXTybnwYhczuYOl7KRskgwlB+rID0WYACxj4wdQ==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@cosmjs/crypto": "^0.30.1",
+        "@cosmjs/encoding": "^0.30.1",
+        "@cosmjs/json-rpc": "^0.30.1",
+        "@cosmjs/math": "^0.30.1",
+        "@cosmjs/socket": "^0.30.1",
+        "@cosmjs/stream": "^0.30.1",
+        "@cosmjs/utils": "^0.30.1",
+        "axios": "^0.21.2",
+        "readonly-date": "^1.0.0",
+        "xstream": "^11.14.0"
+      }
+    },
+    "node_modules/@cosmjs/utils": {
+      "version": "0.30.1",
+      "resolved": "https://registry.npmjs.org/@cosmjs/utils/-/utils-0.30.1.tgz",
+      "integrity": "sha512-KvvX58MGMWh7xA+N+deCfunkA/ZNDvFLw4YbOmX3f/XBIkqrVY7qlotfy2aNb1kgp6h4B6Yc8YawJPDTfvWX7g==",
+      "license": "Apache-2.0"
+    },
+    "node_modules/@cspotcode/source-map-support": {
+      "version": "0.8.1",
+      "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
+      "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
+      "dependencies": {
+        "@jridgewell/trace-mapping": "0.3.9"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@ethereumjs/common": {
+      "version": "2.6.5",
+      "resolved": "https://registry.npmjs.org/@ethereumjs/common/-/common-2.6.5.tgz",
+      "integrity": "sha512-lRyVQOeCDaIVtgfbowla32pzeDv2Obr8oR8Put5RdUBNRGr1VGPGQNGP6elWIpgK3YdpzqTOh4GyUGOureVeeA==",
+      "license": "MIT",
+      "dependencies": {
+        "crc-32": "^1.2.0",
+        "ethereumjs-util": "^7.1.5"
+      }
+    },
+    "node_modules/@ethereumjs/tx": {
+      "version": "3.5.2",
+      "resolved": "https://registry.npmjs.org/@ethereumjs/tx/-/tx-3.5.2.tgz",
+      "integrity": "sha512-gQDNJWKrSDGu2w7w0PzVXVBNMzb7wwdDOmOqczmhNjqFxFuIbhVJDwiGEnxFNC2/b8ifcZzY7MLcluizohRzNw==",
+      "license": "MPL-2.0",
+      "dependencies": {
+        "@ethereumjs/common": "^2.6.4",
+        "ethereumjs-util": "^7.1.5"
+      }
+    },
+    "node_modules/@ethersproject/abi": {
+      "version": "5.7.0",
+      "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.7.0.tgz",
+      "integrity": "sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+        },
+        {
+          "type": "individual",
+          "url": "https://www.buymeacoffee.com/ricmoo"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "@ethersproject/address": "^5.7.0",
+        "@ethersproject/bignumber": "^5.7.0",
+        "@ethersproject/bytes": "^5.7.0",
+        "@ethersproject/constants": "^5.7.0",
+        "@ethersproject/hash": "^5.7.0",
+        "@ethersproject/keccak256": "^5.7.0",
+        "@ethersproject/logger": "^5.7.0",
+        "@ethersproject/properties": "^5.7.0",
+        "@ethersproject/strings": "^5.7.0"
+      }
+    },
+    "node_modules/@ethersproject/abstract-provider": {
+      "version": "5.7.0",
+      "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz",
+      "integrity": "sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+        },
+        {
+          "type": "individual",
+          "url": "https://www.buymeacoffee.com/ricmoo"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "@ethersproject/bignumber": "^5.7.0",
+        "@ethersproject/bytes": "^5.7.0",
+        "@ethersproject/logger": "^5.7.0",
+        "@ethersproject/networks": "^5.7.0",
+        "@ethersproject/properties": "^5.7.0",
+        "@ethersproject/transactions": "^5.7.0",
+        "@ethersproject/web": "^5.7.0"
+      }
+    },
+    "node_modules/@ethersproject/abstract-signer": {
+      "version": "5.7.0",
+      "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.7.0.tgz",
+      "integrity": "sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+        },
+        {
+          "type": "individual",
+          "url": "https://www.buymeacoffee.com/ricmoo"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "@ethersproject/abstract-provider": "^5.7.0",
+        "@ethersproject/bignumber": "^5.7.0",
+        "@ethersproject/bytes": "^5.7.0",
+        "@ethersproject/logger": "^5.7.0",
+        "@ethersproject/properties": "^5.7.0"
+      }
+    },
+    "node_modules/@ethersproject/address": {
+      "version": "5.7.0",
+      "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.7.0.tgz",
+      "integrity": "sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+        },
+        {
+          "type": "individual",
+          "url": "https://www.buymeacoffee.com/ricmoo"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "@ethersproject/bignumber": "^5.7.0",
+        "@ethersproject/bytes": "^5.7.0",
+        "@ethersproject/keccak256": "^5.7.0",
+        "@ethersproject/logger": "^5.7.0",
+        "@ethersproject/rlp": "^5.7.0"
+      }
+    },
+    "node_modules/@ethersproject/base64": {
+      "version": "5.7.0",
+      "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.7.0.tgz",
+      "integrity": "sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+        },
+        {
+          "type": "individual",
+          "url": "https://www.buymeacoffee.com/ricmoo"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "@ethersproject/bytes": "^5.7.0"
+      }
+    },
+    "node_modules/@ethersproject/basex": {
+      "version": "5.7.0",
+      "resolved": "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.7.0.tgz",
+      "integrity": "sha512-ywlh43GwZLv2Voc2gQVTKBoVQ1mti3d8HK5aMxsfu/nRDnMmNqaSJ3r3n85HBByT8OpoY96SXM1FogC533T4zw==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+        },
+        {
+          "type": "individual",
+          "url": "https://www.buymeacoffee.com/ricmoo"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "@ethersproject/bytes": "^5.7.0",
+        "@ethersproject/properties": "^5.7.0"
+      }
+    },
+    "node_modules/@ethersproject/bignumber": {
+      "version": "5.7.0",
+      "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.7.0.tgz",
+      "integrity": "sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+        },
+        {
+          "type": "individual",
+          "url": "https://www.buymeacoffee.com/ricmoo"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "@ethersproject/bytes": "^5.7.0",
+        "@ethersproject/logger": "^5.7.0",
+        "bn.js": "^5.2.1"
+      }
+    },
+    "node_modules/@ethersproject/bytes": {
+      "version": "5.7.0",
+      "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.7.0.tgz",
+      "integrity": "sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+        },
+        {
+          "type": "individual",
+          "url": "https://www.buymeacoffee.com/ricmoo"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "@ethersproject/logger": "^5.7.0"
+      }
+    },
+    "node_modules/@ethersproject/constants": {
+      "version": "5.7.0",
+      "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.7.0.tgz",
+      "integrity": "sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+        },
+        {
+          "type": "individual",
+          "url": "https://www.buymeacoffee.com/ricmoo"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "@ethersproject/bignumber": "^5.7.0"
+      }
+    },
+    "node_modules/@ethersproject/contracts": {
+      "version": "5.7.0",
+      "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.7.0.tgz",
+      "integrity": "sha512-5GJbzEU3X+d33CdfPhcyS+z8MzsTrBGk/sc+G+59+tPa9yFkl6HQ9D6L0QMgNTA9q8dT0XKxxkyp883XsQvbbg==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+        },
+        {
+          "type": "individual",
+          "url": "https://www.buymeacoffee.com/ricmoo"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "@ethersproject/abi": "^5.7.0",
+        "@ethersproject/abstract-provider": "^5.7.0",
+        "@ethersproject/abstract-signer": "^5.7.0",
+        "@ethersproject/address": "^5.7.0",
+        "@ethersproject/bignumber": "^5.7.0",
+        "@ethersproject/bytes": "^5.7.0",
+        "@ethersproject/constants": "^5.7.0",
+        "@ethersproject/logger": "^5.7.0",
+        "@ethersproject/properties": "^5.7.0",
+        "@ethersproject/transactions": "^5.7.0"
+      }
+    },
+    "node_modules/@ethersproject/hash": {
+      "version": "5.7.0",
+      "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.7.0.tgz",
+      "integrity": "sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+        },
+        {
+          "type": "individual",
+          "url": "https://www.buymeacoffee.com/ricmoo"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "@ethersproject/abstract-signer": "^5.7.0",
+        "@ethersproject/address": "^5.7.0",
+        "@ethersproject/base64": "^5.7.0",
+        "@ethersproject/bignumber": "^5.7.0",
+        "@ethersproject/bytes": "^5.7.0",
+        "@ethersproject/keccak256": "^5.7.0",
+        "@ethersproject/logger": "^5.7.0",
+        "@ethersproject/properties": "^5.7.0",
+        "@ethersproject/strings": "^5.7.0"
+      }
+    },
+    "node_modules/@ethersproject/hdnode": {
+      "version": "5.7.0",
+      "resolved": "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.7.0.tgz",
+      "integrity": "sha512-OmyYo9EENBPPf4ERhR7oj6uAtUAhYGqOnIS+jE5pTXvdKBS99ikzq1E7Iv0ZQZ5V36Lqx1qZLeak0Ra16qpeOg==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+        },
+        {
+          "type": "individual",
+          "url": "https://www.buymeacoffee.com/ricmoo"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "@ethersproject/abstract-signer": "^5.7.0",
+        "@ethersproject/basex": "^5.7.0",
+        "@ethersproject/bignumber": "^5.7.0",
+        "@ethersproject/bytes": "^5.7.0",
+        "@ethersproject/logger": "^5.7.0",
+        "@ethersproject/pbkdf2": "^5.7.0",
+        "@ethersproject/properties": "^5.7.0",
+        "@ethersproject/sha2": "^5.7.0",
+        "@ethersproject/signing-key": "^5.7.0",
+        "@ethersproject/strings": "^5.7.0",
+        "@ethersproject/transactions": "^5.7.0",
+        "@ethersproject/wordlists": "^5.7.0"
+      }
+    },
+    "node_modules/@ethersproject/json-wallets": {
+      "version": "5.7.0",
+      "resolved": "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.7.0.tgz",
+      "integrity": "sha512-8oee5Xgu6+RKgJTkvEMl2wDgSPSAQ9MB/3JYjFV9jlKvcYHUXZC+cQp0njgmxdHkYWn8s6/IqIZYm0YWCjO/0g==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+        },
+        {
+          "type": "individual",
+          "url": "https://www.buymeacoffee.com/ricmoo"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "@ethersproject/abstract-signer": "^5.7.0",
+        "@ethersproject/address": "^5.7.0",
+        "@ethersproject/bytes": "^5.7.0",
+        "@ethersproject/hdnode": "^5.7.0",
+        "@ethersproject/keccak256": "^5.7.0",
+        "@ethersproject/logger": "^5.7.0",
+        "@ethersproject/pbkdf2": "^5.7.0",
+        "@ethersproject/properties": "^5.7.0",
+        "@ethersproject/random": "^5.7.0",
+        "@ethersproject/strings": "^5.7.0",
+        "@ethersproject/transactions": "^5.7.0",
+        "aes-js": "3.0.0",
+        "scrypt-js": "3.0.1"
+      }
+    },
+    "node_modules/@ethersproject/keccak256": {
+      "version": "5.7.0",
+      "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.7.0.tgz",
+      "integrity": "sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+        },
+        {
+          "type": "individual",
+          "url": "https://www.buymeacoffee.com/ricmoo"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "@ethersproject/bytes": "^5.7.0",
+        "js-sha3": "0.8.0"
+      }
+    },
+    "node_modules/@ethersproject/logger": {
+      "version": "5.7.0",
+      "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.7.0.tgz",
+      "integrity": "sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+        },
+        {
+          "type": "individual",
+          "url": "https://www.buymeacoffee.com/ricmoo"
+        }
+      ],
+      "license": "MIT"
+    },
+    "node_modules/@ethersproject/networks": {
+      "version": "5.7.1",
+      "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.7.1.tgz",
+      "integrity": "sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+        },
+        {
+          "type": "individual",
+          "url": "https://www.buymeacoffee.com/ricmoo"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "@ethersproject/logger": "^5.7.0"
+      }
+    },
+    "node_modules/@ethersproject/pbkdf2": {
+      "version": "5.7.0",
+      "resolved": "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.7.0.tgz",
+      "integrity": "sha512-oR/dBRZR6GTyaofd86DehG72hY6NpAjhabkhxgr3X2FpJtJuodEl2auADWBZfhDHgVCbu3/H/Ocq2uC6dpNjjw==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+        },
+        {
+          "type": "individual",
+          "url": "https://www.buymeacoffee.com/ricmoo"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "@ethersproject/bytes": "^5.7.0",
+        "@ethersproject/sha2": "^5.7.0"
+      }
+    },
+    "node_modules/@ethersproject/properties": {
+      "version": "5.7.0",
+      "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.7.0.tgz",
+      "integrity": "sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+        },
+        {
+          "type": "individual",
+          "url": "https://www.buymeacoffee.com/ricmoo"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "@ethersproject/logger": "^5.7.0"
+      }
+    },
+    "node_modules/@ethersproject/providers": {
+      "version": "5.7.2",
+      "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.7.2.tgz",
+      "integrity": "sha512-g34EWZ1WWAVgr4aptGlVBF8mhl3VWjv+8hoAnzStu8Ah22VHBsuGzP17eb6xDVRzw895G4W7vvx60lFFur/1Rg==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+        },
+        {
+          "type": "individual",
+          "url": "https://www.buymeacoffee.com/ricmoo"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "@ethersproject/abstract-provider": "^5.7.0",
+        "@ethersproject/abstract-signer": "^5.7.0",
+        "@ethersproject/address": "^5.7.0",
+        "@ethersproject/base64": "^5.7.0",
+        "@ethersproject/basex": "^5.7.0",
+        "@ethersproject/bignumber": "^5.7.0",
+        "@ethersproject/bytes": "^5.7.0",
+        "@ethersproject/constants": "^5.7.0",
+        "@ethersproject/hash": "^5.7.0",
+        "@ethersproject/logger": "^5.7.0",
+        "@ethersproject/networks": "^5.7.0",
+        "@ethersproject/properties": "^5.7.0",
+        "@ethersproject/random": "^5.7.0",
+        "@ethersproject/rlp": "^5.7.0",
+        "@ethersproject/sha2": "^5.7.0",
+        "@ethersproject/strings": "^5.7.0",
+        "@ethersproject/transactions": "^5.7.0",
+        "@ethersproject/web": "^5.7.0",
+        "bech32": "1.1.4",
+        "ws": "7.4.6"
+      }
+    },
+    "node_modules/@ethersproject/providers/node_modules/bech32": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz",
+      "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==",
+      "license": "MIT"
+    },
+    "node_modules/@ethersproject/providers/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==",
+      "license": "MIT",
+      "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/@ethersproject/random": {
+      "version": "5.7.0",
+      "resolved": "https://registry.npmjs.org/@ethersproject/random/-/random-5.7.0.tgz",
+      "integrity": "sha512-19WjScqRA8IIeWclFme75VMXSBvi4e6InrUNuaR4s5pTF2qNhcGdCUwdxUVGtDDqC00sDLCO93jPQoDUH4HVmQ==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+        },
+        {
+          "type": "individual",
+          "url": "https://www.buymeacoffee.com/ricmoo"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "@ethersproject/bytes": "^5.7.0",
+        "@ethersproject/logger": "^5.7.0"
+      }
+    },
+    "node_modules/@ethersproject/rlp": {
+      "version": "5.7.0",
+      "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.7.0.tgz",
+      "integrity": "sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+        },
+        {
+          "type": "individual",
+          "url": "https://www.buymeacoffee.com/ricmoo"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "@ethersproject/bytes": "^5.7.0",
+        "@ethersproject/logger": "^5.7.0"
+      }
+    },
+    "node_modules/@ethersproject/sha2": {
+      "version": "5.7.0",
+      "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.7.0.tgz",
+      "integrity": "sha512-gKlH42riwb3KYp0reLsFTokByAKoJdgFCwI+CCiX/k+Jm2mbNs6oOaCjYQSlI1+XBVejwH2KrmCbMAT/GnRDQw==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+        },
+        {
+          "type": "individual",
+          "url": "https://www.buymeacoffee.com/ricmoo"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "@ethersproject/bytes": "^5.7.0",
+        "@ethersproject/logger": "^5.7.0",
+        "hash.js": "1.1.7"
+      }
+    },
+    "node_modules/@ethersproject/signing-key": {
+      "version": "5.7.0",
+      "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.7.0.tgz",
+      "integrity": "sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+        },
+        {
+          "type": "individual",
+          "url": "https://www.buymeacoffee.com/ricmoo"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "@ethersproject/bytes": "^5.7.0",
+        "@ethersproject/logger": "^5.7.0",
+        "@ethersproject/properties": "^5.7.0",
+        "bn.js": "^5.2.1",
+        "elliptic": "6.5.4",
+        "hash.js": "1.1.7"
+      }
+    },
+    "node_modules/@ethersproject/solidity": {
+      "version": "5.7.0",
+      "resolved": "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.7.0.tgz",
+      "integrity": "sha512-HmabMd2Dt/raavyaGukF4XxizWKhKQ24DoLtdNbBmNKUOPqwjsKQSdV9GQtj9CBEea9DlzETlVER1gYeXXBGaA==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+        },
+        {
+          "type": "individual",
+          "url": "https://www.buymeacoffee.com/ricmoo"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "@ethersproject/bignumber": "^5.7.0",
+        "@ethersproject/bytes": "^5.7.0",
+        "@ethersproject/keccak256": "^5.7.0",
+        "@ethersproject/logger": "^5.7.0",
+        "@ethersproject/sha2": "^5.7.0",
+        "@ethersproject/strings": "^5.7.0"
+      }
+    },
+    "node_modules/@ethersproject/strings": {
+      "version": "5.7.0",
+      "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.7.0.tgz",
+      "integrity": "sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+        },
+        {
+          "type": "individual",
+          "url": "https://www.buymeacoffee.com/ricmoo"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "@ethersproject/bytes": "^5.7.0",
+        "@ethersproject/constants": "^5.7.0",
+        "@ethersproject/logger": "^5.7.0"
+      }
+    },
+    "node_modules/@ethersproject/transactions": {
+      "version": "5.7.0",
+      "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.7.0.tgz",
+      "integrity": "sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+        },
+        {
+          "type": "individual",
+          "url": "https://www.buymeacoffee.com/ricmoo"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "@ethersproject/address": "^5.7.0",
+        "@ethersproject/bignumber": "^5.7.0",
+        "@ethersproject/bytes": "^5.7.0",
+        "@ethersproject/constants": "^5.7.0",
+        "@ethersproject/keccak256": "^5.7.0",
+        "@ethersproject/logger": "^5.7.0",
+        "@ethersproject/properties": "^5.7.0",
+        "@ethersproject/rlp": "^5.7.0",
+        "@ethersproject/signing-key": "^5.7.0"
+      }
+    },
+    "node_modules/@ethersproject/units": {
+      "version": "5.7.0",
+      "resolved": "https://registry.npmjs.org/@ethersproject/units/-/units-5.7.0.tgz",
+      "integrity": "sha512-pD3xLMy3SJu9kG5xDGI7+xhTEmGXlEqXU4OfNapmfnxLVY4EMSSRp7j1k7eezutBPH7RBN/7QPnwR7hzNlEFeg==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+        },
+        {
+          "type": "individual",
+          "url": "https://www.buymeacoffee.com/ricmoo"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "@ethersproject/bignumber": "^5.7.0",
+        "@ethersproject/constants": "^5.7.0",
+        "@ethersproject/logger": "^5.7.0"
+      }
+    },
+    "node_modules/@ethersproject/wallet": {
+      "version": "5.7.0",
+      "resolved": "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.7.0.tgz",
+      "integrity": "sha512-MhmXlJXEJFBFVKrDLB4ZdDzxcBxQ3rLyCkhNqVu3CDYvR97E+8r01UgrI+TI99Le+aYm/in/0vp86guJuM7FCA==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+        },
+        {
+          "type": "individual",
+          "url": "https://www.buymeacoffee.com/ricmoo"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "@ethersproject/abstract-provider": "^5.7.0",
+        "@ethersproject/abstract-signer": "^5.7.0",
+        "@ethersproject/address": "^5.7.0",
+        "@ethersproject/bignumber": "^5.7.0",
+        "@ethersproject/bytes": "^5.7.0",
+        "@ethersproject/hash": "^5.7.0",
+        "@ethersproject/hdnode": "^5.7.0",
+        "@ethersproject/json-wallets": "^5.7.0",
+        "@ethersproject/keccak256": "^5.7.0",
+        "@ethersproject/logger": "^5.7.0",
+        "@ethersproject/properties": "^5.7.0",
+        "@ethersproject/random": "^5.7.0",
+        "@ethersproject/signing-key": "^5.7.0",
+        "@ethersproject/transactions": "^5.7.0",
+        "@ethersproject/wordlists": "^5.7.0"
+      }
+    },
+    "node_modules/@ethersproject/web": {
+      "version": "5.7.1",
+      "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.7.1.tgz",
+      "integrity": "sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+        },
+        {
+          "type": "individual",
+          "url": "https://www.buymeacoffee.com/ricmoo"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "@ethersproject/base64": "^5.7.0",
+        "@ethersproject/bytes": "^5.7.0",
+        "@ethersproject/logger": "^5.7.0",
+        "@ethersproject/properties": "^5.7.0",
+        "@ethersproject/strings": "^5.7.0"
+      }
+    },
+    "node_modules/@ethersproject/wordlists": {
+      "version": "5.7.0",
+      "resolved": "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.7.0.tgz",
+      "integrity": "sha512-S2TFNJNfHWVHNE6cNDjbVlZ6MgE17MIxMbMg2zv3wn+3XSJGosL1m9ZVv3GXCf/2ymSsQ+hRI5IzoMJTG6aoVA==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+        },
+        {
+          "type": "individual",
+          "url": "https://www.buymeacoffee.com/ricmoo"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "@ethersproject/bytes": "^5.7.0",
+        "@ethersproject/hash": "^5.7.0",
+        "@ethersproject/logger": "^5.7.0",
+        "@ethersproject/properties": "^5.7.0",
+        "@ethersproject/strings": "^5.7.0"
+      }
+    },
+    "node_modules/@graphql-typed-document-node/core": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz",
+      "integrity": "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==",
+      "license": "MIT",
+      "peerDependencies": {
+        "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.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==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "browser-headers": "^0.4.1"
+      },
+      "peerDependencies": {
+        "google-protobuf": "^3.14.0"
+      }
+    },
+    "node_modules/@injectivelabs/core-proto-ts": {
+      "version": "0.0.11",
+      "resolved": "https://registry.npmjs.org/@injectivelabs/core-proto-ts/-/core-proto-ts-0.0.11.tgz",
+      "integrity": "sha512-gYMzkoZ0olXLbEhSQVarUCMR6VAHytvENDv2Psjl9EjO5Pg93vTGLViS4E4vA5fezRfdF/x0Uic31w+ogp66jA==",
+      "license": "MIT",
+      "dependencies": {
+        "@injectivelabs/grpc-web": "^0.0.1",
+        "google-protobuf": "^3.14.0",
+        "protobufjs": "^7.0.0",
+        "rxjs": "^7.4.0"
+      }
+    },
+    "node_modules/@injectivelabs/core-proto-ts/node_modules/long": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz",
+      "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==",
+      "license": "Apache-2.0"
+    },
+    "node_modules/@injectivelabs/core-proto-ts/node_modules/protobufjs": {
+      "version": "7.2.3",
+      "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.3.tgz",
+      "integrity": "sha512-TtpvOqwB5Gdz/PQmOjgsrGH1nHjAQVCN7JG4A6r1sXRWESL5rNMAiRcBQlCAdKxZcAbstExQePYG8xof/JVRgg==",
+      "hasInstallScript": true,
+      "license": "BSD-3-Clause",
+      "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/node": ">=13.7.0",
+        "long": "^5.0.0"
+      },
+      "engines": {
+        "node": ">=12.0.0"
+      }
+    },
+    "node_modules/@injectivelabs/exceptions": {
+      "version": "1.10.2",
+      "resolved": "https://registry.npmjs.org/@injectivelabs/exceptions/-/exceptions-1.10.2.tgz",
+      "integrity": "sha512-JLHgU/MjxRYSpn/9G9mJvHuNiA5ze6w86sXz09kQh7tlSaTC4PGqBBbBSu0hrUBBX86O+vk2ULkn1Ks1n7FlOw==",
+      "hasInstallScript": true,
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@injectivelabs/grpc-web": "^0.0.1",
+        "@injectivelabs/ts-types": "^1.10.1",
+        "http-status-codes": "^2.2.0",
+        "link-module-alias": "^1.2.0",
+        "shx": "^0.3.2"
+      }
+    },
+    "node_modules/@injectivelabs/exceptions/dist": {
+      "extraneous": true
+    },
+    "node_modules/@injectivelabs/grpc-web": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/@injectivelabs/grpc-web/-/grpc-web-0.0.1.tgz",
+      "integrity": "sha512-Pu5YgaZp+OvR5UWfqbrPdHer3+gDf+b5fQoY+t2VZx1IAVHX8bzbN9EreYTvTYtFeDpYRWM8P7app2u4EX5wTw==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "browser-headers": "^0.4.1"
+      },
+      "peerDependencies": {
+        "google-protobuf": "^3.14.0"
+      }
+    },
+    "node_modules/@injectivelabs/grpc-web-node-http-transport": {
+      "version": "0.0.2",
+      "resolved": "https://registry.npmjs.org/@injectivelabs/grpc-web-node-http-transport/-/grpc-web-node-http-transport-0.0.2.tgz",
+      "integrity": "sha512-rpyhXLiGY/UMs6v6YmgWHJHiO9l0AgDyVNv+jcutNVt4tQrmNvnpvz2wCAGOFtq5LuX/E9ChtTVpk3gWGqXcGA==",
+      "license": "Apache-2.0",
+      "peerDependencies": {
+        "@injectivelabs/grpc-web": ">=0.0.1"
+      }
+    },
+    "node_modules/@injectivelabs/grpc-web-react-native-transport": {
+      "version": "0.0.2",
+      "resolved": "https://registry.npmjs.org/@injectivelabs/grpc-web-react-native-transport/-/grpc-web-react-native-transport-0.0.2.tgz",
+      "integrity": "sha512-mk+aukQXnYNgPsPnu3KBi+FD0ZHQpazIlaBZ2jNZG7QAVmxTWtv3R66Zoq99Wx2dnE946NsZBYAoa0K5oSjnow==",
+      "license": "Apache-2.0",
+      "peerDependencies": {
+        "@injectivelabs/grpc-web": ">=0.0.1"
+      }
+    },
+    "node_modules/@injectivelabs/indexer-proto-ts": {
+      "version": "0.0.9",
+      "resolved": "https://registry.npmjs.org/@injectivelabs/indexer-proto-ts/-/indexer-proto-ts-0.0.9.tgz",
+      "integrity": "sha512-ZFTUKlHAY2WYnB9RPPf11nq7SNm7wcKFTmFTavTiHV8UvNEni7dCR3Un6U5Mo1qD0xHEsfoCDMdqGcIguliPMA==",
+      "license": "MIT",
+      "dependencies": {
+        "@injectivelabs/grpc-web": "^0.0.1",
+        "google-protobuf": "^3.14.0",
+        "protobufjs": "^7.0.0",
+        "rxjs": "^7.4.0"
+      }
+    },
+    "node_modules/@injectivelabs/indexer-proto-ts/node_modules/long": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz",
+      "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==",
+      "license": "Apache-2.0"
+    },
+    "node_modules/@injectivelabs/indexer-proto-ts/node_modules/protobufjs": {
+      "version": "7.2.3",
+      "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.3.tgz",
+      "integrity": "sha512-TtpvOqwB5Gdz/PQmOjgsrGH1nHjAQVCN7JG4A6r1sXRWESL5rNMAiRcBQlCAdKxZcAbstExQePYG8xof/JVRgg==",
+      "hasInstallScript": true,
+      "license": "BSD-3-Clause",
+      "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/node": ">=13.7.0",
+        "long": "^5.0.0"
+      },
+      "engines": {
+        "node": ">=12.0.0"
+      }
+    },
+    "node_modules/@injectivelabs/mito-proto-ts": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/@injectivelabs/mito-proto-ts/-/mito-proto-ts-1.0.2.tgz",
+      "integrity": "sha512-A/5Nf/RJiBRiwYNqH2K0nNrOuuVcYCebqgEt3btpDfQXcyaHIssjDmZOtmMT1M7P/enEVgDu0auxE7tsmSFijg==",
+      "license": "MIT",
+      "dependencies": {
+        "@injectivelabs/grpc-web": "^0.0.1",
+        "google-protobuf": "^3.14.0",
+        "protobufjs": "^7.0.0",
+        "rxjs": "^7.4.0"
+      }
+    },
+    "node_modules/@injectivelabs/mito-proto-ts/node_modules/long": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz",
+      "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==",
+      "license": "Apache-2.0"
+    },
+    "node_modules/@injectivelabs/mito-proto-ts/node_modules/protobufjs": {
+      "version": "7.2.3",
+      "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.3.tgz",
+      "integrity": "sha512-TtpvOqwB5Gdz/PQmOjgsrGH1nHjAQVCN7JG4A6r1sXRWESL5rNMAiRcBQlCAdKxZcAbstExQePYG8xof/JVRgg==",
+      "hasInstallScript": true,
+      "license": "BSD-3-Clause",
+      "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/node": ">=13.7.0",
+        "long": "^5.0.0"
+      },
+      "engines": {
+        "node": ">=12.0.0"
+      }
+    },
+    "node_modules/@injectivelabs/networks": {
+      "version": "1.10.4",
+      "resolved": "https://registry.npmjs.org/@injectivelabs/networks/-/networks-1.10.4.tgz",
+      "integrity": "sha512-EjWdTXpU+j8YFikxiMacVhPK8dzamMD4czkrst7NfcMRoBCMNMrOp5lItF5GFq0BSx3xu/zfkb2+3wWTIdWUxQ==",
+      "hasInstallScript": true,
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@injectivelabs/exceptions": "^1.10.2",
+        "@injectivelabs/ts-types": "^1.10.1",
+        "@injectivelabs/utils": "^1.10.2",
+        "link-module-alias": "^1.2.0",
+        "shx": "^0.3.2"
+      }
+    },
+    "node_modules/@injectivelabs/networks/dist": {
+      "extraneous": true
+    },
+    "node_modules/@injectivelabs/sdk-ts": {
+      "version": "1.10.37",
+      "resolved": "https://registry.npmjs.org/@injectivelabs/sdk-ts/-/sdk-ts-1.10.37.tgz",
+      "integrity": "sha512-+7LzC1iDiN3oT7PZ3yV2PchsrH1WQfS+tV8/geesi0EBKT4AW4v2Ur3OYhtDXvQia1zSxWJY9phS3iAmaBd9vQ==",
+      "hasInstallScript": true,
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@apollo/client": "^3.5.8",
+        "@cosmjs/amino": "^0.30.1",
+        "@cosmjs/proto-signing": "^0.30.1",
+        "@cosmjs/stargate": "^0.30.1",
+        "@ethersproject/bytes": "^5.7.0",
+        "@injectivelabs/core-proto-ts": "^0.0.11",
+        "@injectivelabs/exceptions": "^1.10.2",
+        "@injectivelabs/grpc-web": "^0.0.1",
+        "@injectivelabs/grpc-web-node-http-transport": "^0.0.2",
+        "@injectivelabs/grpc-web-react-native-transport": "^0.0.2",
+        "@injectivelabs/indexer-proto-ts": "^0.0.9",
+        "@injectivelabs/mito-proto-ts": "1.0.2",
+        "@injectivelabs/networks": "^1.10.4",
+        "@injectivelabs/test-utils": "^1.10.1",
+        "@injectivelabs/token-metadata": "^1.10.17",
+        "@injectivelabs/ts-types": "^1.10.1",
+        "@injectivelabs/utils": "^1.10.2",
+        "@metamask/eth-sig-util": "^4.0.0",
+        "axios": "^0.27.2",
+        "bech32": "^2.0.0",
+        "bip39": "^3.0.4",
+        "cosmjs-types": "^0.7.1",
+        "eth-crypto": "^2.6.0",
+        "ethereumjs-util": "^7.1.4",
+        "ethers": "^5.7.2",
+        "google-protobuf": "^3.21.0",
+        "graphql": "^16.3.0",
+        "http-status-codes": "^2.2.0",
+        "js-sha3": "^0.8.0",
+        "jscrypto": "^1.0.3",
+        "keccak256": "^1.0.6",
+        "link-module-alias": "^1.2.0",
+        "rxjs": "^7.8.0",
+        "secp256k1": "^4.0.3",
+        "shx": "^0.3.2",
+        "snakecase-keys": "^5.4.1"
+      }
+    },
+    "node_modules/@injectivelabs/sdk-ts/dist": {
+      "extraneous": true
+    },
+    "node_modules/@injectivelabs/sdk-ts/node_modules/axios": {
+      "version": "0.27.2",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz",
+      "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==",
+      "license": "MIT",
+      "dependencies": {
+        "follow-redirects": "^1.14.9",
+        "form-data": "^4.0.0"
+      }
+    },
+    "node_modules/@injectivelabs/test-utils": {
+      "version": "1.10.1",
+      "resolved": "https://registry.npmjs.org/@injectivelabs/test-utils/-/test-utils-1.10.1.tgz",
+      "integrity": "sha512-ULP3XJBZN8Muv0jVpo0rfUOD/CDlyg4rij6YuRpYhTg6P0wIlKq9dL36cZlylay+F+4HeLn9qB0D2Cr3+FrhPw==",
+      "hasInstallScript": true,
+      "license": "Apache-2.0",
+      "dependencies": {
+        "axios": "^0.21.1",
+        "bignumber.js": "^9.0.1",
+        "link-module-alias": "^1.2.0",
+        "shx": "^0.3.2",
+        "snakecase-keys": "^5.1.2",
+        "store2": "^2.12.0"
+      }
+    },
+    "node_modules/@injectivelabs/test-utils/dist": {
+      "extraneous": true
+    },
+    "node_modules/@injectivelabs/token-metadata": {
+      "version": "1.10.17",
+      "resolved": "https://registry.npmjs.org/@injectivelabs/token-metadata/-/token-metadata-1.10.17.tgz",
+      "integrity": "sha512-1TFZMs38B21Y0uzqxRuIHifmj6VrJCZLEJnjGuhzIfhtLqSB/ZtCf3JNAarujwwgj6xWb7vzqzqNpo+SIYKvwg==",
+      "hasInstallScript": true,
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@injectivelabs/exceptions": "^1.10.2",
+        "@injectivelabs/networks": "^1.10.4",
+        "@injectivelabs/ts-types": "^1.10.1",
+        "@injectivelabs/utils": "^1.10.2",
+        "@types/lodash.values": "^4.3.6",
+        "copyfiles": "^2.4.1",
+        "jsonschema": "^1.4.0",
+        "link-module-alias": "^1.2.0",
+        "lodash": "^4.17.21",
+        "lodash.values": "^4.3.0",
+        "shx": "^0.3.2"
+      }
+    },
+    "node_modules/@injectivelabs/token-metadata/dist": {
+      "extraneous": true
+    },
+    "node_modules/@injectivelabs/ts-types": {
+      "version": "1.10.1",
+      "resolved": "https://registry.npmjs.org/@injectivelabs/ts-types/-/ts-types-1.10.1.tgz",
+      "integrity": "sha512-gQQjcnRx2TjLmZDMV8IIkRvLtAzTPptJuWKwPCfSlCRKOIv7Eafzy2qFINUIkKDOeu/lZUtSykEsAIUBEmXqFg==",
+      "hasInstallScript": true,
+      "license": "Apache-2.0",
+      "dependencies": {
+        "link-module-alias": "^1.2.0",
+        "shx": "^0.3.2"
+      }
+    },
+    "node_modules/@injectivelabs/ts-types/dist": {
+      "extraneous": true
+    },
+    "node_modules/@injectivelabs/utils": {
+      "version": "1.10.2",
+      "resolved": "https://registry.npmjs.org/@injectivelabs/utils/-/utils-1.10.2.tgz",
+      "integrity": "sha512-XMO7RRbXs06cChr5Wezr0Dbl1Z9hq+ceB4Dn3qyulzupGepeivkoPTcyG4IdjOiwf7PnFeGQ/aVG3hr0rJI7dQ==",
+      "hasInstallScript": true,
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@injectivelabs/exceptions": "^1.10.2",
+        "@injectivelabs/ts-types": "^1.10.1",
+        "axios": "^0.21.1",
+        "bignumber.js": "^9.0.1",
+        "http-status-codes": "^2.2.0",
+        "link-module-alias": "^1.2.0",
+        "shx": "^0.3.2",
+        "snakecase-keys": "^5.1.2",
+        "store2": "^2.12.0"
+      }
+    },
+    "node_modules/@injectivelabs/utils/dist": {
+      "extraneous": true
+    },
+    "node_modules/@jridgewell/resolve-uri": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz",
+      "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==",
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@jridgewell/sourcemap-codec": {
+      "version": "1.4.15",
+      "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
+      "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
+    },
+    "node_modules/@jridgewell/trace-mapping": {
+      "version": "0.3.9",
+      "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
+      "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
+      "dependencies": {
+        "@jridgewell/resolve-uri": "^3.0.3",
+        "@jridgewell/sourcemap-codec": "^1.4.10"
+      }
+    },
+    "node_modules/@metamask/eth-sig-util": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/@metamask/eth-sig-util/-/eth-sig-util-4.0.1.tgz",
+      "integrity": "sha512-tghyZKLHZjcdlDqCA3gNZmLeR0XvOE9U1qoQO9ohyAZT6Pya+H9vkBPcsyXytmYLNgVoin7CKCmweo/R43V+tQ==",
+      "license": "ISC",
+      "dependencies": {
+        "ethereumjs-abi": "^0.6.8",
+        "ethereumjs-util": "^6.2.1",
+        "ethjs-util": "^0.1.6",
+        "tweetnacl": "^1.0.3",
+        "tweetnacl-util": "^0.15.1"
+      },
+      "engines": {
+        "node": ">=12.0.0"
+      }
+    },
+    "node_modules/@metamask/eth-sig-util/node_modules/@types/bn.js": {
+      "version": "4.11.6",
+      "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz",
+      "integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@metamask/eth-sig-util/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==",
+      "license": "MIT"
+    },
+    "node_modules/@metamask/eth-sig-util/node_modules/ethereumjs-util": {
+      "version": "6.2.1",
+      "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.2.1.tgz",
+      "integrity": "sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw==",
+      "license": "MPL-2.0",
+      "dependencies": {
+        "@types/bn.js": "^4.11.3",
+        "bn.js": "^4.11.0",
+        "create-hash": "^1.1.2",
+        "elliptic": "^6.5.2",
+        "ethereum-cryptography": "^0.1.3",
+        "ethjs-util": "0.1.6",
+        "rlp": "^2.2.3"
+      }
+    },
+    "node_modules/@mysten/bcs": {
+      "version": "0.7.1",
+      "resolved": "https://registry.npmjs.org/@mysten/bcs/-/bcs-0.7.1.tgz",
+      "integrity": "sha512-wFPb8bkhwrbiStfZMV5rFM7J+umpke59/dNjDp+UYJKykNlW23LCk2ePyEUvGdb62HGJM1jyOJ8g4egE3OmdKA==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "bs58": "^5.0.0"
+      }
+    },
+    "node_modules/@mysten/bcs/node_modules/base-x": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.0.tgz",
+      "integrity": "sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw==",
+      "license": "MIT"
+    },
+    "node_modules/@mysten/bcs/node_modules/bs58": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/bs58/-/bs58-5.0.0.tgz",
+      "integrity": "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==",
+      "license": "MIT",
+      "dependencies": {
+        "base-x": "^4.0.0"
+      }
+    },
+    "node_modules/@mysten/sui.js": {
+      "version": "0.32.2",
+      "resolved": "https://registry.npmjs.org/@mysten/sui.js/-/sui.js-0.32.2.tgz",
+      "integrity": "sha512-/Hm4xkGolJhqj8FvQr7QSHDTlxIvL52mtbOao9f75YjrBh7y1Uh9kbJSY7xiTF1NY9sv6p5hUVlYRJuM0Hvn9A==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@mysten/bcs": "0.7.1",
+        "@noble/curves": "^1.0.0",
+        "@noble/hashes": "^1.3.0",
+        "@scure/bip32": "^1.3.0",
+        "@scure/bip39": "^1.2.0",
+        "@suchipi/femver": "^1.0.0",
+        "jayson": "^4.0.0",
+        "rpc-websockets": "^7.5.1",
+        "superstruct": "^1.0.3",
+        "tweetnacl": "^1.0.3"
+      },
+      "engines": {
+        "node": ">=16"
+      }
+    },
+    "node_modules/@noble/curves": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.0.0.tgz",
+      "integrity": "sha512-2upgEu0iLiDVDZkNLeFV2+ht0BAVgQnEmCk6JsOch9Rp8xfkMCbvbAZlA2pBHQc73dbl+vFOXfqkf4uemdn0bw==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://paulmillr.com/funding/"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "@noble/hashes": "1.3.0"
+      }
+    },
+    "node_modules/@noble/ed25519": {
+      "version": "1.7.3",
+      "resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-1.7.3.tgz",
+      "integrity": "sha512-iR8GBkDt0Q3GyaVcIu7mSsVIqnFbkbRzGLWlvhwunacoLwt4J3swfKhfaM6rN6WY+TBGoYT1GtT1mIh2/jGbRQ==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://paulmillr.com/funding/"
+        }
+      ],
+      "license": "MIT"
+    },
+    "node_modules/@noble/hashes": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.0.tgz",
+      "integrity": "sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://paulmillr.com/funding/"
+        }
+      ],
+      "license": "MIT"
+    },
+    "node_modules/@noble/secp256k1": {
+      "version": "1.7.1",
+      "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.7.1.tgz",
+      "integrity": "sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://paulmillr.com/funding/"
+        }
+      ],
+      "license": "MIT"
+    },
+    "node_modules/@project-serum/anchor": {
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@project-serum/anchor/-/anchor-0.25.0.tgz",
+      "integrity": "sha512-E6A5Y/ijqpfMJ5psJvbw0kVTzLZFUcOFgs6eSM2M2iWE1lVRF18T6hWZVNl6zqZsoz98jgnNHtVGJMs+ds9A7A==",
+      "license": "(MIT OR Apache-2.0)",
+      "dependencies": {
+        "@project-serum/borsh": "^0.2.5",
+        "@solana/web3.js": "^1.36.0",
+        "base64-js": "^1.5.1",
+        "bn.js": "^5.1.2",
+        "bs58": "^4.0.1",
+        "buffer-layout": "^1.2.2",
+        "camelcase": "^5.3.1",
+        "cross-fetch": "^3.1.5",
+        "crypto-hash": "^1.3.0",
+        "eventemitter3": "^4.0.7",
+        "js-sha256": "^0.9.0",
+        "pako": "^2.0.3",
+        "snake-case": "^3.0.4",
+        "superstruct": "^0.15.4",
+        "toml": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=11"
+      }
+    },
+    "node_modules/@project-serum/anchor/node_modules/superstruct": {
+      "version": "0.15.5",
+      "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-0.15.5.tgz",
+      "integrity": "sha512-4AOeU+P5UuE/4nOUkmcQdW5y7i9ndt1cQd/3iUe+LTz3RxESf/W/5lg4B74HbDMMv8PHnPnGCQFH45kBcrQYoQ==",
+      "license": "MIT"
+    },
+    "node_modules/@project-serum/borsh": {
+      "version": "0.2.5",
+      "resolved": "https://registry.npmjs.org/@project-serum/borsh/-/borsh-0.2.5.tgz",
+      "integrity": "sha512-UmeUkUoKdQ7rhx6Leve1SssMR/Ghv8qrEiyywyxSWg7ooV7StdpPBhciiy5eB3T0qU1BXvdRNC8TdrkxK7WC5Q==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "bn.js": "^5.1.2",
+        "buffer-layout": "^1.2.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "peerDependencies": {
+        "@solana/web3.js": "^1.2.0"
+      }
+    },
+    "node_modules/@protobufjs/aspromise": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
+      "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
+      "license": "BSD-3-Clause"
+    },
+    "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==",
+      "license": "BSD-3-Clause"
+    },
+    "node_modules/@protobufjs/codegen": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
+      "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==",
+      "license": "BSD-3-Clause"
+    },
+    "node_modules/@protobufjs/eventemitter": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
+      "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==",
+      "license": "BSD-3-Clause"
+    },
+    "node_modules/@protobufjs/fetch": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
+      "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==",
+      "license": "BSD-3-Clause",
+      "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": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
+      "license": "BSD-3-Clause"
+    },
+    "node_modules/@protobufjs/inquire": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
+      "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==",
+      "license": "BSD-3-Clause"
+    },
+    "node_modules/@protobufjs/path": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
+      "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
+      "license": "BSD-3-Clause"
+    },
+    "node_modules/@protobufjs/pool": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
+      "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
+      "license": "BSD-3-Clause"
+    },
+    "node_modules/@protobufjs/utf8": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
+      "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==",
+      "license": "BSD-3-Clause"
+    },
+    "node_modules/@scure/base": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz",
+      "integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://paulmillr.com/funding/"
+        }
+      ],
+      "license": "MIT"
+    },
+    "node_modules/@scure/bip32": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.0.tgz",
+      "integrity": "sha512-bcKpo1oj54hGholplGLpqPHRbIsnbixFtc06nwuNM5/dwSXOq/AAYoIBRsBmnZJSdfeNW5rnff7NTAz3ZCqR9Q==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://paulmillr.com/funding/"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "@noble/curves": "~1.0.0",
+        "@noble/hashes": "~1.3.0",
+        "@scure/base": "~1.1.0"
+      }
+    },
+    "node_modules/@scure/bip39": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.0.tgz",
+      "integrity": "sha512-SX/uKq52cuxm4YFXWFaVByaSHJh2w3BnokVSeUJVCv6K7WulT9u2BuNRBhuFl8vAuYnzx9bEu9WgpcNYTrYieg==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://paulmillr.com/funding/"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "@noble/hashes": "~1.3.0",
+        "@scure/base": "~1.1.0"
+      }
+    },
+    "node_modules/@solana/buffer-layout": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/@solana/buffer-layout/-/buffer-layout-4.0.1.tgz",
+      "integrity": "sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA==",
+      "license": "MIT",
+      "dependencies": {
+        "buffer": "~6.0.3"
+      },
+      "engines": {
+        "node": ">=5.10"
+      }
+    },
+    "node_modules/@solana/buffer-layout-utils": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/@solana/buffer-layout-utils/-/buffer-layout-utils-0.2.0.tgz",
+      "integrity": "sha512-szG4sxgJGktbuZYDg2FfNmkMi0DYQoVjN2h7ta1W1hPrwzarcFLBq9UpX1UjNXsNpT9dn+chgprtWGioUAr4/g==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@solana/buffer-layout": "^4.0.0",
+        "@solana/web3.js": "^1.32.0",
+        "bigint-buffer": "^1.1.5",
+        "bignumber.js": "^9.0.1"
+      },
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/@solana/spl-token": {
+      "version": "0.3.7",
+      "resolved": "https://registry.npmjs.org/@solana/spl-token/-/spl-token-0.3.7.tgz",
+      "integrity": "sha512-bKGxWTtIw6VDdCBngjtsGlKGLSmiu/8ghSt/IOYJV24BsymRbgq7r12GToeetpxmPaZYLddKwAz7+EwprLfkfg==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@solana/buffer-layout": "^4.0.0",
+        "@solana/buffer-layout-utils": "^0.2.0",
+        "buffer": "^6.0.3"
+      },
+      "engines": {
+        "node": ">=16"
+      },
+      "peerDependencies": {
+        "@solana/web3.js": "^1.47.4"
+      }
+    },
+    "node_modules/@solana/web3.js": {
+      "version": "1.75.0",
+      "resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.75.0.tgz",
+      "integrity": "sha512-rHQgdo1EWfb+nPUpHe4O7i8qJPELHKNR5PAZRK+a7XxiykqOfbaAlPt5boDWAGPnYbSv0ziWZv5mq9DlFaQCxg==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.12.5",
+        "@noble/ed25519": "^1.7.0",
+        "@noble/hashes": "^1.1.2",
+        "@noble/secp256k1": "^1.6.3",
+        "@solana/buffer-layout": "^4.0.0",
+        "agentkeepalive": "^4.2.1",
+        "bigint-buffer": "^1.1.5",
+        "bn.js": "^5.0.0",
+        "borsh": "^0.7.0",
+        "bs58": "^4.0.1",
+        "buffer": "6.0.3",
+        "fast-stable-stringify": "^1.0.0",
+        "jayson": "^3.4.4",
+        "node-fetch": "^2.6.7",
+        "rpc-websockets": "^7.5.1",
+        "superstruct": "^0.14.2"
+      }
+    },
+    "node_modules/@solana/web3.js/node_modules/@types/node": {
+      "version": "12.20.55",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz",
+      "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==",
+      "license": "MIT"
+    },
+    "node_modules/@solana/web3.js/node_modules/jayson": {
+      "version": "3.7.0",
+      "resolved": "https://registry.npmjs.org/jayson/-/jayson-3.7.0.tgz",
+      "integrity": "sha512-tfy39KJMrrXJ+mFcMpxwBvFDetS8LAID93+rycFglIQM4kl3uNR3W4lBLE/FFhsoUCEox5Dt2adVpDm/XtebbQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/connect": "^3.4.33",
+        "@types/node": "^12.12.54",
+        "@types/ws": "^7.4.4",
+        "commander": "^2.20.3",
+        "delay": "^5.0.0",
+        "es6-promisify": "^5.0.0",
+        "eyes": "^0.1.8",
+        "isomorphic-ws": "^4.0.1",
+        "json-stringify-safe": "^5.0.1",
+        "JSONStream": "^1.3.5",
+        "lodash": "^4.17.20",
+        "uuid": "^8.3.2",
+        "ws": "^7.4.5"
+      },
+      "bin": {
+        "jayson": "bin/jayson.js"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/@solana/web3.js/node_modules/superstruct": {
+      "version": "0.14.2",
+      "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-0.14.2.tgz",
+      "integrity": "sha512-nPewA6m9mR3d6k7WkZ8N8zpTWfenFH3q9pA2PkuiZxINr9DKB2+40wEQf0ixn8VaGuJ78AB6iWOtStI+/4FKZQ==",
+      "license": "MIT"
+    },
+    "node_modules/@suchipi/femver": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/@suchipi/femver/-/femver-1.0.0.tgz",
+      "integrity": "sha512-bprE8+K5V+DPX7q2e2K57ImqNBdfGHDIWaGI5xHxZoxbKOuQZn4wzPiUxOAHnsUr3w3xHrWXwN7gnG/iIuEMIg==",
+      "license": "MIT"
+    },
+    "node_modules/@terra-money/legacy.proto": {
+      "name": "@terra-money/terra.proto",
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/@terra-money/terra.proto/-/terra.proto-0.1.7.tgz",
+      "integrity": "sha512-NXD7f6pQCulvo6+mv6MAPzhOkUzRjgYVuHZE/apih+lVnPG5hDBU0rRYnOGGofwvKT5/jQoOENnFn/gioWWnyQ==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "google-protobuf": "^3.17.3",
+        "long": "^4.0.0",
+        "protobufjs": "~6.11.2"
+      }
+    },
+    "node_modules/@terra-money/terra.js": {
+      "version": "3.1.8",
+      "resolved": "https://registry.npmjs.org/@terra-money/terra.js/-/terra.js-3.1.8.tgz",
+      "integrity": "sha512-Cd/fh4MswT00fDGVckoZ0cm77EpIy4+CjSDO0RqZ3Qfp4CJBp7sWTLRNsyzUWjdYOT5iTx+1wOMCYbbyKo6LAw==",
+      "license": "MIT",
+      "dependencies": {
+        "@classic-terra/terra.proto": "^1.1.0",
+        "@terra-money/terra.proto": "^2.1.0",
+        "axios": "^0.27.2",
+        "bech32": "^2.0.0",
+        "bip32": "^2.0.6",
+        "bip39": "^3.0.3",
+        "bufferutil": "^4.0.3",
+        "decimal.js": "^10.2.1",
+        "jscrypto": "^1.0.1",
+        "readable-stream": "^3.6.0",
+        "secp256k1": "^4.0.2",
+        "tmp": "^0.2.1",
+        "utf-8-validate": "^5.0.5",
+        "ws": "^7.5.9"
+      },
+      "engines": {
+        "node": ">=14"
+      }
+    },
+    "node_modules/@terra-money/terra.js/node_modules/axios": {
+      "version": "0.27.2",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz",
+      "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==",
+      "license": "MIT",
+      "dependencies": {
+        "follow-redirects": "^1.14.9",
+        "form-data": "^4.0.0"
+      }
+    },
+    "node_modules/@terra-money/terra.proto": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/@terra-money/terra.proto/-/terra.proto-2.1.0.tgz",
+      "integrity": "sha512-rhaMslv3Rkr+QsTQEZs64FKA4QlfO0DfQHaR6yct/EovenMkibDEQ63dEL6yJA6LCaEQGYhyVB9JO9pTUA8ybw==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@improbable-eng/grpc-web": "^0.14.1",
+        "google-protobuf": "^3.17.3",
+        "long": "^4.0.0",
+        "protobufjs": "~6.11.2"
+      }
+    },
+    "node_modules/@tsconfig/node10": {
+      "version": "1.0.9",
+      "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
+      "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA=="
+    },
+    "node_modules/@tsconfig/node12": {
+      "version": "1.0.11",
+      "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
+      "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag=="
+    },
+    "node_modules/@tsconfig/node14": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
+      "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow=="
+    },
+    "node_modules/@tsconfig/node16": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz",
+      "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ=="
+    },
+    "node_modules/@types/bn.js": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.1.tgz",
+      "integrity": "sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@types/chai": {
+      "version": "4.3.4",
+      "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.4.tgz",
+      "integrity": "sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@types/connect": {
+      "version": "3.4.35",
+      "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz",
+      "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@types/json5": {
+      "version": "0.0.29",
+      "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
+      "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
+      "license": "MIT",
+      "optional": true
+    },
+    "node_modules/@types/lodash": {
+      "version": "4.14.192",
+      "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.192.tgz",
+      "integrity": "sha512-km+Vyn3BYm5ytMO13k9KTp27O75rbQ0NFw+U//g+PX7VZyjCioXaRFisqSIJRECljcTv73G3i6BpglNGHgUQ5A==",
+      "license": "MIT"
+    },
+    "node_modules/@types/lodash.values": {
+      "version": "4.3.7",
+      "resolved": "https://registry.npmjs.org/@types/lodash.values/-/lodash.values-4.3.7.tgz",
+      "integrity": "sha512-Moex9/sWxtKEa+BKiH5zvmhfcieDlcz4wRxMhO/oJ2qOKUdujoU6dQjUTxWA8jwEREpHXmiY4HCwNRpycW8JQA==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/lodash": "*"
+      }
+    },
+    "node_modules/@types/long": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz",
+      "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==",
+      "license": "MIT"
+    },
+    "node_modules/@types/mocha": {
+      "version": "10.0.1",
+      "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.1.tgz",
+      "integrity": "sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@types/node": {
+      "version": "18.15.11",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.11.tgz",
+      "integrity": "sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==",
+      "license": "MIT"
+    },
+    "node_modules/@types/pbkdf2": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/@types/pbkdf2/-/pbkdf2-3.1.0.tgz",
+      "integrity": "sha512-Cf63Rv7jCQ0LaL8tNXmEyqTHuIJxRdlS5vMh1mj5voN4+QFhVZnlZruezqpWYDiJ8UTzhP0VmeLXCmBk66YrMQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@types/secp256k1": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/@types/secp256k1/-/secp256k1-4.0.3.tgz",
+      "integrity": "sha512-Da66lEIFeIz9ltsdMZcpQvmrmmoqrfju8pm1BH8WbYjZSwUgCwXLb9C+9XYogwBITnbsSaMdVPb2ekf7TV+03w==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@types/ws": {
+      "version": "7.4.7",
+      "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz",
+      "integrity": "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@wry/context": {
+      "version": "0.7.0",
+      "resolved": "https://registry.npmjs.org/@wry/context/-/context-0.7.0.tgz",
+      "integrity": "sha512-LcDAiYWRtwAoSOArfk7cuYvFXytxfVrdX7yxoUmK7pPITLk5jYh2F8knCwS7LjgYL8u1eidPlKKV6Ikqq0ODqQ==",
+      "license": "MIT",
+      "dependencies": {
+        "tslib": "^2.3.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/@wry/equality": {
+      "version": "0.5.3",
+      "resolved": "https://registry.npmjs.org/@wry/equality/-/equality-0.5.3.tgz",
+      "integrity": "sha512-avR+UXdSrsF2v8vIqIgmeTY0UR91UT+IyablCyKe/uk22uOJ8fusKZnH9JH9e1/EtLeNJBtagNmL3eJdnOV53g==",
+      "license": "MIT",
+      "dependencies": {
+        "tslib": "^2.3.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/@wry/trie": {
+      "version": "0.3.2",
+      "resolved": "https://registry.npmjs.org/@wry/trie/-/trie-0.3.2.tgz",
+      "integrity": "sha512-yRTyhWSls2OY/pYLfwff867r8ekooZ4UI+/gxot5Wj8EFwSf2rG+n+Mo/6LoLQm1TKA4GRj2+LCpbfS937dClQ==",
+      "license": "MIT",
+      "dependencies": {
+        "tslib": "^2.3.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/@xpla/xpla.js": {
+      "version": "0.2.3",
+      "resolved": "https://registry.npmjs.org/@xpla/xpla.js/-/xpla.js-0.2.3.tgz",
+      "integrity": "sha512-Tfk7hCGWXtwr08reY3Pi6dmzIqFbzri9jcyzJdfNmdo4cN0PMwpRJuZZcPmtxiIUnNef3AN1E/6nJUD5MKniuA==",
+      "license": "MIT",
+      "dependencies": {
+        "@ethersproject/bytes": "^5.6.1",
+        "@ethersproject/keccak256": "^5.6.1",
+        "@ethersproject/signing-key": "^5.6.2",
+        "@terra-money/legacy.proto": "npm:@terra-money/terra.proto@^0.1.7",
+        "@terra-money/terra.proto": "^2.1.0",
+        "axios": "^0.26.1",
+        "bech32": "^2.0.0",
+        "bip32": "^2.0.6",
+        "bip39": "^3.0.3",
+        "bufferutil": "^4.0.3",
+        "crypto-addr-codec": "^0.1.7",
+        "decimal.js": "^10.2.1",
+        "elliptic": "^6.5.4",
+        "ethereumjs-util": "^7.1.5",
+        "jscrypto": "^1.0.1",
+        "readable-stream": "^3.6.0",
+        "secp256k1": "^4.0.2",
+        "tmp": "^0.2.1",
+        "utf-8-validate": "^5.0.5",
+        "ws": "^7.5.8"
+      },
+      "engines": {
+        "node": ">=14"
+      }
+    },
+    "node_modules/@xpla/xpla.js/node_modules/axios": {
+      "version": "0.26.1",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz",
+      "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==",
+      "license": "MIT",
+      "dependencies": {
+        "follow-redirects": "^1.14.8"
+      }
+    },
+    "node_modules/acorn": {
+      "version": "7.1.1",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz",
+      "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==",
+      "license": "MIT",
+      "bin": {
+        "acorn": "bin/acorn"
+      },
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
+    "node_modules/acorn-walk": {
+      "version": "8.2.0",
+      "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz",
+      "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==",
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
+    "node_modules/aes-js": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz",
+      "integrity": "sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==",
+      "license": "MIT"
+    },
+    "node_modules/agentkeepalive": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.3.0.tgz",
+      "integrity": "sha512-7Epl1Blf4Sy37j4v9f9FjICCh4+KAQOyXgHEwlyBiAQLbhKdq/i2QQU3amQalS/wPhdPzDXPL5DMR5bkn+YeWg==",
+      "license": "MIT",
+      "dependencies": {
+        "debug": "^4.1.0",
+        "depd": "^2.0.0",
+        "humanize-ms": "^1.2.1"
+      },
+      "engines": {
+        "node": ">= 8.0.0"
+      }
+    },
+    "node_modules/algo-msgpack-with-bigint": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/algo-msgpack-with-bigint/-/algo-msgpack-with-bigint-2.1.1.tgz",
+      "integrity": "sha512-F1tGh056XczEaEAqu7s+hlZUDWwOBT70Eq0lfMpBP2YguSQVyxRbprLq5rELXKQOyOaixTWYhMeMQMzP0U5FoQ==",
+      "license": "ISC",
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/algosdk": {
+      "version": "1.24.1",
+      "resolved": "https://registry.npmjs.org/algosdk/-/algosdk-1.24.1.tgz",
+      "integrity": "sha512-9moZxdqeJ6GdE4N6fA/GlUP4LrbLZMYcYkt141J4Ss68OfEgH9qW0wBuZ3ZOKEx/xjc5bg7mLP2Gjg7nwrkmww==",
+      "license": "MIT",
+      "dependencies": {
+        "algo-msgpack-with-bigint": "^2.1.1",
+        "buffer": "^6.0.2",
+        "cross-fetch": "^3.1.5",
+        "hi-base32": "^0.5.1",
+        "js-sha256": "^0.9.0",
+        "js-sha3": "^0.8.0",
+        "js-sha512": "^0.8.0",
+        "json-bigint": "^1.0.0",
+        "tweetnacl": "^1.0.3",
+        "vlq": "^2.0.4"
+      },
+      "engines": {
+        "node": ">=14.0.0"
+      }
+    },
+    "node_modules/ansi-colors": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz",
+      "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/ansi-regex": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "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==",
+      "license": "MIT",
+      "dependencies": {
+        "color-convert": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+      }
+    },
+    "node_modules/anymatch": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+      "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+      "license": "ISC",
+      "dependencies": {
+        "normalize-path": "^3.0.0",
+        "picomatch": "^2.0.4"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/aptos": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/aptos/-/aptos-1.5.0.tgz",
+      "integrity": "sha512-N7OuRtU7IYHkDkNx+4QS3g/QQGCp+36KzYn3oXPmT7Kttfuv+UKliQVdjy3cLmwd/DCQSh9ObTovwdxnHjUn0g==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@noble/hashes": "1.1.3",
+        "@scure/bip39": "1.1.0",
+        "axios": "0.27.2",
+        "form-data": "4.0.0",
+        "tweetnacl": "1.0.3"
+      },
+      "engines": {
+        "node": ">=11.0.0"
+      }
+    },
+    "node_modules/aptos/node_modules/@noble/hashes": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.1.3.tgz",
+      "integrity": "sha512-CE0FCR57H2acVI5UOzIGSSIYxZ6v/HOhDR0Ro9VLyhnzLwx0o8W1mmgaqlEUx4049qJDlIBRztv5k+MM8vbO3A==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://paulmillr.com/funding/"
+        }
+      ],
+      "license": "MIT"
+    },
+    "node_modules/aptos/node_modules/@scure/bip39": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.1.0.tgz",
+      "integrity": "sha512-pwrPOS16VeTKg98dYXQyIjJEcWfz7/1YJIwxUEPFfQPtc86Ym/1sVgQ2RLoD43AazMk2l/unK4ITySSpW2+82w==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://paulmillr.com/funding/"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "@noble/hashes": "~1.1.1",
+        "@scure/base": "~1.1.0"
+      }
+    },
+    "node_modules/aptos/node_modules/@scure/bip39/node_modules/@noble/hashes": {
+      "version": "1.1.5",
+      "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.1.5.tgz",
+      "integrity": "sha512-LTMZiiLc+V4v1Yi16TD6aX2gmtKszNye0pQgbaLqkvhIqP7nVsSaJsWloGQjJfJ8offaoP5GtX3yY5swbcJxxQ==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://paulmillr.com/funding/"
+        }
+      ],
+      "license": "MIT"
+    },
+    "node_modules/aptos/node_modules/axios": {
+      "version": "0.27.2",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz",
+      "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==",
+      "license": "MIT",
+      "dependencies": {
+        "follow-redirects": "^1.14.9",
+        "form-data": "^4.0.0"
+      }
+    },
+    "node_modules/arg": {
+      "version": "4.1.3",
+      "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
+      "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA=="
+    },
+    "node_modules/argparse": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+      "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+      "license": "Python-2.0"
+    },
+    "node_modules/arrify": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
+      "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/assertion-error": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
+      "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
+      "license": "MIT",
+      "engines": {
+        "node": "*"
+      }
+    },
+    "node_modules/asynckit": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+      "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+      "license": "MIT"
+    },
+    "node_modules/axios": {
+      "version": "0.21.4",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
+      "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
+      "license": "MIT",
+      "dependencies": {
+        "follow-redirects": "^1.14.0"
+      }
+    },
+    "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==",
+      "license": "MIT"
+    },
+    "node_modules/base-x": {
+      "version": "3.0.9",
+      "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz",
+      "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==",
+      "license": "MIT",
+      "dependencies": {
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "node_modules/base64-js": {
+      "version": "1.5.1",
+      "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+      "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "MIT"
+    },
+    "node_modules/bech32": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz",
+      "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==",
+      "license": "MIT"
+    },
+    "node_modules/big-integer": {
+      "version": "1.6.36",
+      "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.36.tgz",
+      "integrity": "sha512-t70bfa7HYEA1D9idDbmuv7YbsbVkQ+Hp+8KFSul4aE5e/i1bjCNIRYJZlA8Q8p0r9T8cF/RVvwUgRA//FydEyg==",
+      "license": "Unlicense",
+      "engines": {
+        "node": ">=0.6"
+      }
+    },
+    "node_modules/bigint-buffer": {
+      "version": "1.1.5",
+      "resolved": "https://registry.npmjs.org/bigint-buffer/-/bigint-buffer-1.1.5.tgz",
+      "integrity": "sha512-trfYco6AoZ+rKhKnxA0hgX0HAbVP/s808/EuDSe2JDzUnCp/xAsli35Orvk67UrTEcwuxZqYZDmfA2RXJgxVvA==",
+      "hasInstallScript": true,
+      "license": "Apache-2.0",
+      "dependencies": {
+        "bindings": "^1.3.0"
+      },
+      "engines": {
+        "node": ">= 10.0.0"
+      }
+    },
+    "node_modules/bignumber.js": {
+      "version": "9.1.1",
+      "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz",
+      "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==",
+      "license": "MIT",
+      "engines": {
+        "node": "*"
+      }
+    },
+    "node_modules/binary-extensions": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
+      "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/binary-parser": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/binary-parser/-/binary-parser-2.2.1.tgz",
+      "integrity": "sha512-5ATpz/uPDgq5GgEDxTB4ouXCde7q2lqAQlSdBRQVl/AJnxmQmhIfyxJx+0MGu//D5rHQifkfGbWWlaysG0o9NA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/bindings": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
+      "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
+      "license": "MIT",
+      "dependencies": {
+        "file-uri-to-path": "1.0.0"
+      }
+    },
+    "node_modules/bip32": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/bip32/-/bip32-2.0.6.tgz",
+      "integrity": "sha512-HpV5OMLLGTjSVblmrtYRfFFKuQB+GArM0+XP8HGWfJ5vxYBqo+DesvJwOdC2WJ3bCkZShGf0QIfoIpeomVzVdA==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "10.12.18",
+        "bs58check": "^2.1.1",
+        "create-hash": "^1.2.0",
+        "create-hmac": "^1.1.7",
+        "tiny-secp256k1": "^1.1.3",
+        "typeforce": "^1.11.5",
+        "wif": "^2.0.6"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/bip32/node_modules/@types/node": {
+      "version": "10.12.18",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.18.tgz",
+      "integrity": "sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ==",
+      "license": "MIT"
+    },
+    "node_modules/bip39": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.1.0.tgz",
+      "integrity": "sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A==",
+      "license": "ISC",
+      "dependencies": {
+        "@noble/hashes": "^1.2.0"
+      }
+    },
+    "node_modules/bip66": {
+      "version": "1.1.5",
+      "resolved": "https://registry.npmjs.org/bip66/-/bip66-1.1.5.tgz",
+      "integrity": "sha512-nemMHz95EmS38a26XbbdxIYj5csHd3RMP3H5bwQknX0WYHF01qhpufP42mLOwVICuH2JmhIhXiWs89MfUGL7Xw==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "node_modules/blakejs": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.2.1.tgz",
+      "integrity": "sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==",
+      "license": "MIT"
+    },
+    "node_modules/bn.js": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz",
+      "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==",
+      "license": "MIT"
+    },
+    "node_modules/borsh": {
+      "version": "0.7.0",
+      "resolved": "https://registry.npmjs.org/borsh/-/borsh-0.7.0.tgz",
+      "integrity": "sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "bn.js": "^5.2.0",
+        "bs58": "^4.0.0",
+        "text-encoding-utf-8": "^1.0.2"
+      }
+    },
+    "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==",
+      "license": "MIT",
+      "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==",
+      "license": "MIT",
+      "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": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==",
+      "license": "MIT"
+    },
+    "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==",
+      "license": "Apache-2.0"
+    },
+    "node_modules/browser-stdout": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
+      "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
+      "license": "ISC"
+    },
+    "node_modules/browserify-aes": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
+      "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
+      "license": "MIT",
+      "dependencies": {
+        "buffer-xor": "^1.0.3",
+        "cipher-base": "^1.0.0",
+        "create-hash": "^1.1.0",
+        "evp_bytestokey": "^1.0.3",
+        "inherits": "^2.0.1",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "node_modules/bs58": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz",
+      "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==",
+      "license": "MIT",
+      "dependencies": {
+        "base-x": "^3.0.2"
+      }
+    },
+    "node_modules/bs58check": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz",
+      "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==",
+      "license": "MIT",
+      "dependencies": {
+        "bs58": "^4.0.0",
+        "create-hash": "^1.1.0",
+        "safe-buffer": "^5.1.2"
+      }
+    },
+    "node_modules/buffer": {
+      "version": "6.0.3",
+      "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
+      "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "base64-js": "^1.3.1",
+        "ieee754": "^1.2.1"
+      }
+    },
+    "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=="
+    },
+    "node_modules/buffer-layout": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/buffer-layout/-/buffer-layout-1.2.2.tgz",
+      "integrity": "sha512-kWSuLN694+KTk8SrYvCqwP2WcgQjoRCiF5b4QDvkkz8EmgD+aWAIceGFKMIAdmF/pH+vpgNV3d3kAKorcdAmWA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=4.5"
+      }
+    },
+    "node_modules/buffer-xor": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
+      "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==",
+      "license": "MIT"
+    },
+    "node_modules/bufferutil": {
+      "version": "4.0.7",
+      "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.7.tgz",
+      "integrity": "sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==",
+      "hasInstallScript": true,
+      "license": "MIT",
+      "dependencies": {
+        "node-gyp-build": "^4.3.0"
+      },
+      "engines": {
+        "node": ">=6.14.2"
+      }
+    },
+    "node_modules/camelcase": {
+      "version": "5.3.1",
+      "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+      "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/capability": {
+      "version": "0.2.5",
+      "resolved": "https://registry.npmjs.org/capability/-/capability-0.2.5.tgz",
+      "integrity": "sha512-rsJZYVCgXd08sPqwmaIqjAd5SUTfonV0z/gDJ8D6cN8wQphky1kkAYEqQ+hmDxTw7UihvBfjUVUSY+DBEe44jg==",
+      "license": "MIT"
+    },
+    "node_modules/chai": {
+      "version": "4.3.7",
+      "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz",
+      "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==",
+      "license": "MIT",
+      "dependencies": {
+        "assertion-error": "^1.1.0",
+        "check-error": "^1.0.2",
+        "deep-eql": "^4.1.2",
+        "get-func-name": "^2.0.0",
+        "loupe": "^2.3.1",
+        "pathval": "^1.1.1",
+        "type-detect": "^4.0.5"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/chalk": {
+      "version": "4.1.2",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+      "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+      "license": "MIT",
+      "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/chalk/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==",
+      "license": "MIT",
+      "dependencies": {
+        "has-flag": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/check-error": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
+      "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==",
+      "license": "MIT",
+      "engines": {
+        "node": "*"
+      }
+    },
+    "node_modules/chokidar": {
+      "version": "3.5.3",
+      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
+      "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://paulmillr.com/funding/"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "anymatch": "~3.1.2",
+        "braces": "~3.0.2",
+        "glob-parent": "~5.1.2",
+        "is-binary-path": "~2.1.0",
+        "is-glob": "~4.0.1",
+        "normalize-path": "~3.0.0",
+        "readdirp": "~3.6.0"
+      },
+      "engines": {
+        "node": ">= 8.10.0"
+      },
+      "optionalDependencies": {
+        "fsevents": "~2.3.2"
+      }
+    },
+    "node_modules/cipher-base": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
+      "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==",
+      "license": "MIT",
+      "dependencies": {
+        "inherits": "^2.0.1",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "node_modules/cliui": {
+      "version": "7.0.4",
+      "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
+      "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
+      "license": "ISC",
+      "dependencies": {
+        "string-width": "^4.2.0",
+        "strip-ansi": "^6.0.0",
+        "wrap-ansi": "^7.0.0"
+      }
+    },
+    "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==",
+      "license": "MIT",
+      "dependencies": {
+        "color-name": "~1.1.4"
+      },
+      "engines": {
+        "node": ">=7.0.0"
+      }
+    },
+    "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==",
+      "license": "MIT"
+    },
+    "node_modules/combined-stream": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+      "license": "MIT",
+      "dependencies": {
+        "delayed-stream": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "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==",
+      "license": "MIT"
+    },
+    "node_modules/concat-map": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+      "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+      "license": "MIT"
+    },
+    "node_modules/copyfiles": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/copyfiles/-/copyfiles-2.4.1.tgz",
+      "integrity": "sha512-fereAvAvxDrQDOXybk3Qu3dPbOoKoysFMWtkY3mv5BsL8//OSZVL5DCLYqgRfY5cWirgRzlC+WSrxp6Bo3eNZg==",
+      "license": "MIT",
+      "dependencies": {
+        "glob": "^7.0.5",
+        "minimatch": "^3.0.3",
+        "mkdirp": "^1.0.4",
+        "noms": "0.0.0",
+        "through2": "^2.0.1",
+        "untildify": "^4.0.0",
+        "yargs": "^16.1.0"
+      },
+      "bin": {
+        "copyfiles": "copyfiles",
+        "copyup": "copyfiles"
+      }
+    },
+    "node_modules/copyfiles/node_modules/mkdirp": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
+      "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
+      "license": "MIT",
+      "bin": {
+        "mkdirp": "bin/cmd.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/core-util-is": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
+      "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
+      "license": "MIT"
+    },
+    "node_modules/cosmjs-types": {
+      "version": "0.7.2",
+      "resolved": "https://registry.npmjs.org/cosmjs-types/-/cosmjs-types-0.7.2.tgz",
+      "integrity": "sha512-vf2uLyktjr/XVAgEq0DjMxeAWh1yYREe7AMHDKd7EiHVqxBPCaBS+qEEQUkXbR9ndnckqr1sUG8BQhazh4X5lA==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "long": "^4.0.0",
+        "protobufjs": "~6.11.2"
+      }
+    },
+    "node_modules/crc-32": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
+      "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
+      "license": "Apache-2.0",
+      "bin": {
+        "crc32": "bin/crc32.njs"
+      },
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
+    "node_modules/create-hash": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
+      "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
+      "license": "MIT",
+      "dependencies": {
+        "cipher-base": "^1.0.1",
+        "inherits": "^2.0.1",
+        "md5.js": "^1.3.4",
+        "ripemd160": "^2.0.1",
+        "sha.js": "^2.4.0"
+      }
+    },
+    "node_modules/create-hmac": {
+      "version": "1.1.7",
+      "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
+      "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
+      "license": "MIT",
+      "dependencies": {
+        "cipher-base": "^1.0.3",
+        "create-hash": "^1.1.0",
+        "inherits": "^2.0.1",
+        "ripemd160": "^2.0.0",
+        "safe-buffer": "^5.0.1",
+        "sha.js": "^2.4.8"
+      }
+    },
+    "node_modules/create-require": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
+      "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ=="
+    },
+    "node_modules/cross-fetch": {
+      "version": "3.1.5",
+      "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz",
+      "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==",
+      "license": "MIT",
+      "dependencies": {
+        "node-fetch": "2.6.7"
+      }
+    },
+    "node_modules/cross-fetch/node_modules/node-fetch": {
+      "version": "2.6.7",
+      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
+      "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
+      "license": "MIT",
+      "dependencies": {
+        "whatwg-url": "^5.0.0"
+      },
+      "engines": {
+        "node": "4.x || >=6.0.0"
+      },
+      "peerDependencies": {
+        "encoding": "^0.1.0"
+      },
+      "peerDependenciesMeta": {
+        "encoding": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/crypto-addr-codec": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/crypto-addr-codec/-/crypto-addr-codec-0.1.7.tgz",
+      "integrity": "sha512-X4hzfBzNhy4mAc3UpiXEC/L0jo5E8wAa9unsnA8nNXYzXjCcGk83hfC5avJWCSGT8V91xMnAS9AKMHmjw5+XCg==",
+      "license": "MIT",
+      "dependencies": {
+        "base-x": "^3.0.8",
+        "big-integer": "1.6.36",
+        "blakejs": "^1.1.0",
+        "bs58": "^4.0.1",
+        "ripemd160-min": "0.0.6",
+        "safe-buffer": "^5.2.0",
+        "sha3": "^2.1.1"
+      }
+    },
+    "node_modules/crypto-hash": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/crypto-hash/-/crypto-hash-1.3.0.tgz",
+      "integrity": "sha512-lyAZ0EMyjDkVvz8WOeVnuCPvKVBXcMv1l5SVqO1yC7PzTwrD/pPje/BIRbWhMoPe436U+Y2nD7f5bFx0kt+Sbg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/debug": {
+      "version": "4.3.4",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+      "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+      "license": "MIT",
+      "dependencies": {
+        "ms": "2.1.2"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/debug/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==",
+      "license": "MIT"
+    },
+    "node_modules/decamelize": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz",
+      "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/decimal.js": {
+      "version": "10.4.3",
+      "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz",
+      "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==",
+      "license": "MIT"
+    },
+    "node_modules/deep-eql": {
+      "version": "4.1.3",
+      "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz",
+      "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==",
+      "license": "MIT",
+      "dependencies": {
+        "type-detect": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/define-properties": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz",
+      "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==",
+      "license": "MIT",
+      "dependencies": {
+        "has-property-descriptors": "^1.0.0",
+        "object-keys": "^1.1.1"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/delay": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/delay/-/delay-5.0.0.tgz",
+      "integrity": "sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/delayed-stream": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+      "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
+    "node_modules/depd": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+      "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/diff": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz",
+      "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==",
+      "license": "BSD-3-Clause",
+      "engines": {
+        "node": ">=0.3.1"
+      }
+    },
+    "node_modules/dot-case": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz",
+      "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==",
+      "license": "MIT",
+      "dependencies": {
+        "no-case": "^3.0.4",
+        "tslib": "^2.0.3"
+      }
+    },
+    "node_modules/drbg.js": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/drbg.js/-/drbg.js-1.0.1.tgz",
+      "integrity": "sha512-F4wZ06PvqxYLFEZKkFxTDcns9oFNk34hvmJSEwdzsxVQ8YI5YaxtACgQatkYgv2VI2CFkUd2Y+xosPQnHv809g==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "browserify-aes": "^1.0.6",
+        "create-hash": "^1.1.2",
+        "create-hmac": "^1.1.4"
+      },
+      "engines": {
+        "node": ">=0.10"
+      }
+    },
+    "node_modules/eccrypto": {
+      "version": "1.1.6",
+      "resolved": "https://registry.npmjs.org/eccrypto/-/eccrypto-1.1.6.tgz",
+      "integrity": "sha512-d78ivVEzu7Tn0ZphUUaL43+jVPKTMPFGtmgtz1D0LrFn7cY3K8CdrvibuLz2AAkHBLKZtR8DMbB2ukRYFk987A==",
+      "hasInstallScript": true,
+      "license": "CC0-1.0",
+      "dependencies": {
+        "acorn": "7.1.1",
+        "elliptic": "6.5.4",
+        "es6-promise": "4.2.8",
+        "nan": "2.14.0"
+      },
+      "optionalDependencies": {
+        "secp256k1": "3.7.1"
+      }
+    },
+    "node_modules/eccrypto/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==",
+      "license": "MIT",
+      "optional": true
+    },
+    "node_modules/eccrypto/node_modules/nan": {
+      "version": "2.14.0",
+      "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz",
+      "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==",
+      "license": "MIT"
+    },
+    "node_modules/eccrypto/node_modules/secp256k1": {
+      "version": "3.7.1",
+      "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-3.7.1.tgz",
+      "integrity": "sha512-1cf8sbnRreXrQFdH6qsg2H71Xw91fCCS9Yp021GnUNJzWJS/py96fS4lHbnTnouLp08Xj6jBoBB6V78Tdbdu5g==",
+      "hasInstallScript": true,
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "bindings": "^1.5.0",
+        "bip66": "^1.1.5",
+        "bn.js": "^4.11.8",
+        "create-hash": "^1.2.0",
+        "drbg.js": "^1.0.1",
+        "elliptic": "^6.4.1",
+        "nan": "^2.14.0",
+        "safe-buffer": "^5.1.2"
+      },
+      "engines": {
+        "node": ">=4.0.0"
+      }
+    },
+    "node_modules/eccrypto/node_modules/secp256k1/node_modules/nan": {
+      "version": "2.17.0",
+      "resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz",
+      "integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==",
+      "license": "MIT",
+      "optional": 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==",
+      "license": "MIT",
+      "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/elliptic/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==",
+      "license": "MIT"
+    },
+    "node_modules/emoji-regex": {
+      "version": "8.0.0",
+      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+      "license": "MIT"
+    },
+    "node_modules/error-polyfill": {
+      "version": "0.1.3",
+      "resolved": "https://registry.npmjs.org/error-polyfill/-/error-polyfill-0.1.3.tgz",
+      "integrity": "sha512-XHJk60ufE+TG/ydwp4lilOog549iiQF2OAPhkk9DdiYWMrltz5yhDz/xnKuenNwP7gy3dsibssO5QpVhkrSzzg==",
+      "license": "MIT",
+      "dependencies": {
+        "capability": "^0.2.5",
+        "o3": "^1.0.3",
+        "u3": "^0.1.1"
+      }
+    },
+    "node_modules/es6-promise": {
+      "version": "4.2.8",
+      "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
+      "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==",
+      "license": "MIT"
+    },
+    "node_modules/es6-promisify": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz",
+      "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==",
+      "license": "MIT",
+      "dependencies": {
+        "es6-promise": "^4.0.3"
+      }
+    },
+    "node_modules/escalade": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
+      "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/escape-string-regexp": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+      "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/eth-crypto": {
+      "version": "2.6.0",
+      "resolved": "https://registry.npmjs.org/eth-crypto/-/eth-crypto-2.6.0.tgz",
+      "integrity": "sha512-GCX4ffFYRUGgnuWR5qxcZIRQJ1KEqPFiyXU9yVy7s6dtXIMlUXZQ2h+5ID6rFaOHWbpJbjfkC6YdhwtwRYCnug==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "7.20.13",
+        "@ethereumjs/tx": "3.5.2",
+        "@types/bn.js": "5.1.1",
+        "eccrypto": "1.1.6",
+        "ethereumjs-util": "7.1.5",
+        "ethers": "5.7.2",
+        "secp256k1": "5.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/pubkey"
+      }
+    },
+    "node_modules/eth-crypto/node_modules/@babel/runtime": {
+      "version": "7.20.13",
+      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.13.tgz",
+      "integrity": "sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==",
+      "license": "MIT",
+      "dependencies": {
+        "regenerator-runtime": "^0.13.11"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/eth-crypto/node_modules/node-addon-api": {
+      "version": "5.1.0",
+      "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz",
+      "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==",
+      "license": "MIT"
+    },
+    "node_modules/eth-crypto/node_modules/secp256k1": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-5.0.0.tgz",
+      "integrity": "sha512-TKWX8xvoGHrxVdqbYeZM9w+izTF4b9z3NhSaDkdn81btvuh+ivbIMGT/zQvDtTFWhRlThpoz6LEYTr7n8A5GcA==",
+      "hasInstallScript": true,
+      "license": "MIT",
+      "dependencies": {
+        "elliptic": "^6.5.4",
+        "node-addon-api": "^5.0.0",
+        "node-gyp-build": "^4.2.0"
+      },
+      "engines": {
+        "node": ">=14.0.0"
+      }
+    },
+    "node_modules/ethereum-cryptography": {
+      "version": "0.1.3",
+      "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz",
+      "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/pbkdf2": "^3.0.0",
+        "@types/secp256k1": "^4.0.1",
+        "blakejs": "^1.1.0",
+        "browserify-aes": "^1.2.0",
+        "bs58check": "^2.1.2",
+        "create-hash": "^1.2.0",
+        "create-hmac": "^1.1.7",
+        "hash.js": "^1.1.7",
+        "keccak": "^3.0.0",
+        "pbkdf2": "^3.0.17",
+        "randombytes": "^2.1.0",
+        "safe-buffer": "^5.1.2",
+        "scrypt-js": "^3.0.0",
+        "secp256k1": "^4.0.1",
+        "setimmediate": "^1.0.5"
+      }
+    },
+    "node_modules/ethereumjs-abi": {
+      "version": "0.6.8",
+      "resolved": "https://registry.npmjs.org/ethereumjs-abi/-/ethereumjs-abi-0.6.8.tgz",
+      "integrity": "sha512-Tx0r/iXI6r+lRsdvkFDlut0N08jWMnKRZ6Gkq+Nmw75lZe4e6o3EkSnkaBP5NF6+m5PTGAr9JP43N3LyeoglsA==",
+      "license": "MIT",
+      "dependencies": {
+        "bn.js": "^4.11.8",
+        "ethereumjs-util": "^6.0.0"
+      }
+    },
+    "node_modules/ethereumjs-abi/node_modules/@types/bn.js": {
+      "version": "4.11.6",
+      "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz",
+      "integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
+    "node_modules/ethereumjs-abi/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==",
+      "license": "MIT"
+    },
+    "node_modules/ethereumjs-abi/node_modules/ethereumjs-util": {
+      "version": "6.2.1",
+      "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.2.1.tgz",
+      "integrity": "sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw==",
+      "license": "MPL-2.0",
+      "dependencies": {
+        "@types/bn.js": "^4.11.3",
+        "bn.js": "^4.11.0",
+        "create-hash": "^1.1.2",
+        "elliptic": "^6.5.2",
+        "ethereum-cryptography": "^0.1.3",
+        "ethjs-util": "0.1.6",
+        "rlp": "^2.2.3"
+      }
+    },
+    "node_modules/ethereumjs-util": {
+      "version": "7.1.5",
+      "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz",
+      "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==",
+      "license": "MPL-2.0",
+      "dependencies": {
+        "@types/bn.js": "^5.1.0",
+        "bn.js": "^5.1.2",
+        "create-hash": "^1.1.2",
+        "ethereum-cryptography": "^0.1.3",
+        "rlp": "^2.2.4"
+      },
+      "engines": {
+        "node": ">=10.0.0"
+      }
+    },
+    "node_modules/ethers": {
+      "version": "5.7.2",
+      "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.7.2.tgz",
+      "integrity": "sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+        },
+        {
+          "type": "individual",
+          "url": "https://www.buymeacoffee.com/ricmoo"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "@ethersproject/abi": "5.7.0",
+        "@ethersproject/abstract-provider": "5.7.0",
+        "@ethersproject/abstract-signer": "5.7.0",
+        "@ethersproject/address": "5.7.0",
+        "@ethersproject/base64": "5.7.0",
+        "@ethersproject/basex": "5.7.0",
+        "@ethersproject/bignumber": "5.7.0",
+        "@ethersproject/bytes": "5.7.0",
+        "@ethersproject/constants": "5.7.0",
+        "@ethersproject/contracts": "5.7.0",
+        "@ethersproject/hash": "5.7.0",
+        "@ethersproject/hdnode": "5.7.0",
+        "@ethersproject/json-wallets": "5.7.0",
+        "@ethersproject/keccak256": "5.7.0",
+        "@ethersproject/logger": "5.7.0",
+        "@ethersproject/networks": "5.7.1",
+        "@ethersproject/pbkdf2": "5.7.0",
+        "@ethersproject/properties": "5.7.0",
+        "@ethersproject/providers": "5.7.2",
+        "@ethersproject/random": "5.7.0",
+        "@ethersproject/rlp": "5.7.0",
+        "@ethersproject/sha2": "5.7.0",
+        "@ethersproject/signing-key": "5.7.0",
+        "@ethersproject/solidity": "5.7.0",
+        "@ethersproject/strings": "5.7.0",
+        "@ethersproject/transactions": "5.7.0",
+        "@ethersproject/units": "5.7.0",
+        "@ethersproject/wallet": "5.7.0",
+        "@ethersproject/web": "5.7.1",
+        "@ethersproject/wordlists": "5.7.0"
+      }
+    },
+    "node_modules/ethjs-util": {
+      "version": "0.1.6",
+      "resolved": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.6.tgz",
+      "integrity": "sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w==",
+      "license": "MIT",
+      "dependencies": {
+        "is-hex-prefixed": "1.0.0",
+        "strip-hex-prefix": "1.0.0"
+      },
+      "engines": {
+        "node": ">=6.5.0",
+        "npm": ">=3"
+      }
+    },
+    "node_modules/eventemitter3": {
+      "version": "4.0.7",
+      "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
+      "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
+      "license": "MIT"
+    },
+    "node_modules/evp_bytestokey": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
+      "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==",
+      "license": "MIT",
+      "dependencies": {
+        "md5.js": "^1.3.4",
+        "safe-buffer": "^5.1.1"
+      }
+    },
+    "node_modules/eyes": {
+      "version": "0.1.8",
+      "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz",
+      "integrity": "sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==",
+      "engines": {
+        "node": "> 0.1.90"
+      }
+    },
+    "node_modules/fast-stable-stringify": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/fast-stable-stringify/-/fast-stable-stringify-1.0.0.tgz",
+      "integrity": "sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag==",
+      "license": "MIT"
+    },
+    "node_modules/file-uri-to-path": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
+      "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
+      "license": "MIT"
+    },
+    "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==",
+      "license": "MIT",
+      "dependencies": {
+        "to-regex-range": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/find-up": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+      "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+      "license": "MIT",
+      "dependencies": {
+        "locate-path": "^6.0.0",
+        "path-exists": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/flat": {
+      "version": "5.0.2",
+      "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
+      "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==",
+      "license": "BSD-3-Clause",
+      "bin": {
+        "flat": "cli.js"
+      }
+    },
+    "node_modules/follow-redirects": {
+      "version": "1.15.2",
+      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
+      "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://github.com/sponsors/RubenVerborgh"
+        }
+      ],
+      "license": "MIT",
+      "engines": {
+        "node": ">=4.0"
+      },
+      "peerDependenciesMeta": {
+        "debug": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/form-data": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+      "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+      "license": "MIT",
+      "dependencies": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.8",
+        "mime-types": "^2.1.12"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/fs.realpath": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+      "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+      "license": "ISC"
+    },
+    "node_modules/function-bind": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+      "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+      "license": "MIT"
+    },
+    "node_modules/get-caller-file": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+      "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+      "license": "ISC",
+      "engines": {
+        "node": "6.* || 8.* || >= 10.*"
+      }
+    },
+    "node_modules/get-func-name": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz",
+      "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==",
+      "license": "MIT",
+      "engines": {
+        "node": "*"
+      }
+    },
+    "node_modules/get-intrinsic": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz",
+      "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==",
+      "license": "MIT",
+      "dependencies": {
+        "function-bind": "^1.1.1",
+        "has": "^1.0.3",
+        "has-symbols": "^1.0.3"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/glob": {
+      "version": "7.2.3",
+      "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+      "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+      "license": "ISC",
+      "dependencies": {
+        "fs.realpath": "^1.0.0",
+        "inflight": "^1.0.4",
+        "inherits": "2",
+        "minimatch": "^3.1.1",
+        "once": "^1.3.0",
+        "path-is-absolute": "^1.0.0"
+      },
+      "engines": {
+        "node": "*"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/glob-parent": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+      "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+      "license": "ISC",
+      "dependencies": {
+        "is-glob": "^4.0.1"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/globalthis": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz",
+      "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==",
+      "license": "MIT",
+      "dependencies": {
+        "define-properties": "^1.1.3"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/google-protobuf": {
+      "version": "3.21.2",
+      "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.21.2.tgz",
+      "integrity": "sha512-3MSOYFO5U9mPGikIYCzK0SaThypfGgS6bHqrUGXG3DPHCrb+txNqeEcns1W0lkGfk0rCyNXm7xB9rMxnCiZOoA==",
+      "license": "(BSD-3-Clause AND Apache-2.0)"
+    },
+    "node_modules/graphql": {
+      "version": "16.6.0",
+      "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.6.0.tgz",
+      "integrity": "sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw==",
+      "license": "MIT",
+      "engines": {
+        "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0"
+      }
+    },
+    "node_modules/graphql-tag": {
+      "version": "2.12.6",
+      "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.6.tgz",
+      "integrity": "sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==",
+      "license": "MIT",
+      "dependencies": {
+        "tslib": "^2.1.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "peerDependencies": {
+        "graphql": "^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0"
+      }
+    },
+    "node_modules/has": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+      "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+      "license": "MIT",
+      "dependencies": {
+        "function-bind": "^1.1.1"
+      },
+      "engines": {
+        "node": ">= 0.4.0"
+      }
+    },
+    "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==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/has-property-descriptors": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz",
+      "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==",
+      "license": "MIT",
+      "dependencies": {
+        "get-intrinsic": "^1.1.1"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/has-symbols": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
+      "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/hash-base": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz",
+      "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==",
+      "license": "MIT",
+      "dependencies": {
+        "inherits": "^2.0.4",
+        "readable-stream": "^3.6.0",
+        "safe-buffer": "^5.2.0"
+      },
+      "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==",
+      "license": "MIT",
+      "dependencies": {
+        "inherits": "^2.0.3",
+        "minimalistic-assert": "^1.0.1"
+      }
+    },
+    "node_modules/he": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+      "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+      "license": "MIT",
+      "bin": {
+        "he": "bin/he"
+      }
+    },
+    "node_modules/hi-base32": {
+      "version": "0.5.1",
+      "resolved": "https://registry.npmjs.org/hi-base32/-/hi-base32-0.5.1.tgz",
+      "integrity": "sha512-EmBBpvdYh/4XxsnUybsPag6VikPYnN30td+vQk+GI3qpahVEG9+gTkG0aXVxTjBqQ5T6ijbWIu77O+C5WFWsnA==",
+      "license": "MIT"
+    },
+    "node_modules/hmac-drbg": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
+      "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==",
+      "license": "MIT",
+      "dependencies": {
+        "hash.js": "^1.0.3",
+        "minimalistic-assert": "^1.0.0",
+        "minimalistic-crypto-utils": "^1.0.1"
+      }
+    },
+    "node_modules/hoist-non-react-statics": {
+      "version": "3.3.2",
+      "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
+      "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "react-is": "^16.7.0"
+      }
+    },
+    "node_modules/http-errors": {
+      "version": "1.8.1",
+      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz",
+      "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==",
+      "license": "MIT",
+      "dependencies": {
+        "depd": "~1.1.2",
+        "inherits": "2.0.4",
+        "setprototypeof": "1.2.0",
+        "statuses": ">= 1.5.0 < 2",
+        "toidentifier": "1.0.1"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/http-errors/node_modules/depd": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
+      "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/http-status-codes": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.2.0.tgz",
+      "integrity": "sha512-feERVo9iWxvnejp3SEfm/+oNG517npqL2/PIA8ORjyOZjGC7TwCRQsZylciLS64i6pJ0wRYz3rkXLRwbtFa8Ng==",
+      "license": "MIT"
+    },
+    "node_modules/humanize-ms": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
+      "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==",
+      "license": "MIT",
+      "dependencies": {
+        "ms": "^2.0.0"
+      }
+    },
+    "node_modules/ieee754": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+      "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "BSD-3-Clause"
+    },
+    "node_modules/inflight": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+      "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+      "license": "ISC",
+      "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==",
+      "license": "ISC"
+    },
+    "node_modules/interpret": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz",
+      "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
+    "node_modules/is-binary-path": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+      "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+      "license": "MIT",
+      "dependencies": {
+        "binary-extensions": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/is-core-module": {
+      "version": "2.11.0",
+      "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz",
+      "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==",
+      "license": "MIT",
+      "dependencies": {
+        "has": "^1.0.3"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/is-extglob": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+      "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-fullwidth-code-point": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+      "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/is-glob": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+      "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+      "license": "MIT",
+      "dependencies": {
+        "is-extglob": "^2.1.1"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-hex-prefixed": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz",
+      "integrity": "sha512-WvtOiug1VFrE9v1Cydwm+FnXd3+w9GaeVUss5W4v/SLy3UW00vP+6iNF2SdnfiBoLy4bTqVdkftNGTUeOFVsbA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.5.0",
+        "npm": ">=3"
+      }
+    },
+    "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==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.12.0"
+      }
+    },
+    "node_modules/is-plain-obj": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
+      "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/is-unicode-supported": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
+      "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/isarray": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+      "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==",
+      "license": "MIT"
+    },
+    "node_modules/isomorphic-ws": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz",
+      "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==",
+      "license": "MIT",
+      "peerDependencies": {
+        "ws": "*"
+      }
+    },
+    "node_modules/jayson": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/jayson/-/jayson-4.0.0.tgz",
+      "integrity": "sha512-v2RNpDCMu45fnLzSk47vx7I+QUaOsox6f5X0CUlabAFwxoP+8MfAY0NQRFwOEYXIxm8Ih5y6OaEa5KYiQMkyAA==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/connect": "^3.4.33",
+        "@types/node": "^12.12.54",
+        "@types/ws": "^7.4.4",
+        "commander": "^2.20.3",
+        "delay": "^5.0.0",
+        "es6-promisify": "^5.0.0",
+        "eyes": "^0.1.8",
+        "isomorphic-ws": "^4.0.1",
+        "json-stringify-safe": "^5.0.1",
+        "JSONStream": "^1.3.5",
+        "uuid": "^8.3.2",
+        "ws": "^7.4.5"
+      },
+      "bin": {
+        "jayson": "bin/jayson.js"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/jayson/node_modules/@types/node": {
+      "version": "12.20.55",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz",
+      "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==",
+      "license": "MIT"
+    },
+    "node_modules/js-base64": {
+      "version": "3.7.5",
+      "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.5.tgz",
+      "integrity": "sha512-3MEt5DTINKqfScXKfJFrRbxkrnk2AxPWGBL/ycjz4dK8iqiSJ06UxD8jh8xuh6p10TX4t2+7FsBYVxxQbMg+qA==",
+      "license": "BSD-3-Clause"
+    },
+    "node_modules/js-sha256": {
+      "version": "0.9.0",
+      "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz",
+      "integrity": "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==",
+      "license": "MIT"
+    },
+    "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==",
+      "license": "MIT"
+    },
+    "node_modules/js-sha512": {
+      "version": "0.8.0",
+      "resolved": "https://registry.npmjs.org/js-sha512/-/js-sha512-0.8.0.tgz",
+      "integrity": "sha512-PWsmefG6Jkodqt+ePTvBZCSMFgN7Clckjd0O7su3I0+BW2QWUTJNzjktHsztGLhncP2h8mcF9V9Y2Ha59pAViQ==",
+      "license": "MIT"
+    },
+    "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==",
+      "license": "MIT"
+    },
+    "node_modules/js-yaml": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+      "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+      "license": "MIT",
+      "dependencies": {
+        "argparse": "^2.0.1"
+      },
+      "bin": {
+        "js-yaml": "bin/js-yaml.js"
+      }
+    },
+    "node_modules/jscrypto": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/jscrypto/-/jscrypto-1.0.3.tgz",
+      "integrity": "sha512-lryZl0flhodv4SZHOqyb1bx5sKcJxj0VBo0Kzb4QMAg3L021IC9uGpl0RCZa+9KJwlRGSK2C80ITcwbe19OKLQ==",
+      "license": "MIT",
+      "bin": {
+        "jscrypto": "bin/cli.js"
+      }
+    },
+    "node_modules/json-bigint": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz",
+      "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==",
+      "license": "MIT",
+      "dependencies": {
+        "bignumber.js": "^9.0.0"
+      }
+    },
+    "node_modules/json-stringify-safe": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+      "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==",
+      "license": "ISC"
+    },
+    "node_modules/json5": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
+      "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "minimist": "^1.2.0"
+      },
+      "bin": {
+        "json5": "lib/cli.js"
+      }
+    },
+    "node_modules/jsonparse": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz",
+      "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==",
+      "engines": [
+        "node >= 0.2.0"
+      ],
+      "license": "MIT"
+    },
+    "node_modules/jsonschema": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.4.1.tgz",
+      "integrity": "sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ==",
+      "license": "MIT",
+      "engines": {
+        "node": "*"
+      }
+    },
+    "node_modules/JSONStream": {
+      "version": "1.3.5",
+      "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz",
+      "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==",
+      "license": "(MIT OR Apache-2.0)",
+      "dependencies": {
+        "jsonparse": "^1.2.0",
+        "through": ">=2.2.7 <3"
+      },
+      "bin": {
+        "JSONStream": "bin.js"
+      },
+      "engines": {
+        "node": "*"
+      }
+    },
+    "node_modules/keccak": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.3.tgz",
+      "integrity": "sha512-JZrLIAJWuZxKbCilMpNz5Vj7Vtb4scDG3dMXLOsbzBmQGyjwE61BbW7bJkfKKCShXiQZt3T6sBgALRtmd+nZaQ==",
+      "hasInstallScript": true,
+      "license": "MIT",
+      "dependencies": {
+        "node-addon-api": "^2.0.0",
+        "node-gyp-build": "^4.2.0",
+        "readable-stream": "^3.6.0"
+      },
+      "engines": {
+        "node": ">=10.0.0"
+      }
+    },
+    "node_modules/keccak256": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/keccak256/-/keccak256-1.0.6.tgz",
+      "integrity": "sha512-8GLiM01PkdJVGUhR1e6M/AvWnSqYS0HaERI+K/QtStGDGlSTx2B1zTqZk4Zlqu5TxHJNTxWAdP9Y+WI50OApUw==",
+      "license": "MIT",
+      "dependencies": {
+        "bn.js": "^5.2.0",
+        "buffer": "^6.0.3",
+        "keccak": "^3.0.2"
+      }
+    },
+    "node_modules/libsodium": {
+      "version": "0.7.11",
+      "resolved": "https://registry.npmjs.org/libsodium/-/libsodium-0.7.11.tgz",
+      "integrity": "sha512-WPfJ7sS53I2s4iM58QxY3Inb83/6mjlYgcmZs7DJsvDlnmVUwNinBCi5vBT43P6bHRy01O4zsMU2CoVR6xJ40A==",
+      "license": "ISC"
+    },
+    "node_modules/libsodium-wrappers": {
+      "version": "0.7.11",
+      "resolved": "https://registry.npmjs.org/libsodium-wrappers/-/libsodium-wrappers-0.7.11.tgz",
+      "integrity": "sha512-SrcLtXj7BM19vUKtQuyQKiQCRJPgbpauzl3s0rSwD+60wtHqSUuqcoawlMDheCJga85nKOQwxNYQxf/CKAvs6Q==",
+      "license": "ISC",
+      "dependencies": {
+        "libsodium": "^0.7.11"
+      }
+    },
+    "node_modules/link-module-alias": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/link-module-alias/-/link-module-alias-1.2.0.tgz",
+      "integrity": "sha512-ahPjXepbSVKbahTB6LxR//VHm8HPfI+QQygCH+E82spBY4HR5VPJTvlhKBc9F7muVxnS6C1rRfoPOXAbWO/fyw==",
+      "license": "MIT",
+      "dependencies": {
+        "chalk": "^2.4.1"
+      },
+      "bin": {
+        "link-module-alias": "index.js"
+      },
+      "engines": {
+        "node": "> 8.0.0"
+      }
+    },
+    "node_modules/link-module-alias/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==",
+      "license": "MIT",
+      "dependencies": {
+        "color-convert": "^1.9.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/link-module-alias/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==",
+      "license": "MIT",
+      "dependencies": {
+        "ansi-styles": "^3.2.1",
+        "escape-string-regexp": "^1.0.5",
+        "supports-color": "^5.3.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/link-module-alias/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==",
+      "license": "MIT",
+      "dependencies": {
+        "color-name": "1.1.3"
+      }
+    },
+    "node_modules/link-module-alias/node_modules/color-name": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+      "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
+      "license": "MIT"
+    },
+    "node_modules/link-module-alias/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": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.8.0"
+      }
+    },
+    "node_modules/link-module-alias/node_modules/has-flag": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+      "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/link-module-alias/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==",
+      "license": "MIT",
+      "dependencies": {
+        "has-flag": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/locate-path": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+      "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+      "license": "MIT",
+      "dependencies": {
+        "p-locate": "^5.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/lodash": {
+      "version": "4.17.21",
+      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+      "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+      "license": "MIT"
+    },
+    "node_modules/lodash.values": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/lodash.values/-/lodash.values-4.3.0.tgz",
+      "integrity": "sha512-r0RwvdCv8id9TUblb/O7rYPwVy6lerCbcawrfdo9iC/1t1wsNMJknO79WNBgwkH0hIeJ08jmvvESbFpNb4jH0Q==",
+      "license": "MIT"
+    },
+    "node_modules/log-symbols": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
+      "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==",
+      "license": "MIT",
+      "dependencies": {
+        "chalk": "^4.1.0",
+        "is-unicode-supported": "^0.1.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/long": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
+      "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==",
+      "license": "Apache-2.0"
+    },
+    "node_modules/loose-envify": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+      "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+      "license": "MIT",
+      "dependencies": {
+        "js-tokens": "^3.0.0 || ^4.0.0"
+      },
+      "bin": {
+        "loose-envify": "cli.js"
+      }
+    },
+    "node_modules/loupe": {
+      "version": "2.3.6",
+      "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz",
+      "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==",
+      "license": "MIT",
+      "dependencies": {
+        "get-func-name": "^2.0.0"
+      }
+    },
+    "node_modules/lower-case": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz",
+      "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==",
+      "license": "MIT",
+      "dependencies": {
+        "tslib": "^2.0.3"
+      }
+    },
+    "node_modules/make-error": {
+      "version": "1.3.6",
+      "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
+      "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
+      "license": "ISC"
+    },
+    "node_modules/map-obj": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz",
+      "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/md5.js": {
+      "version": "1.3.5",
+      "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
+      "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==",
+      "license": "MIT",
+      "dependencies": {
+        "hash-base": "^3.0.0",
+        "inherits": "^2.0.1",
+        "safe-buffer": "^5.1.2"
+      }
+    },
+    "node_modules/mime-db": {
+      "version": "1.52.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mime-types": {
+      "version": "2.1.35",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+      "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+      "license": "MIT",
+      "dependencies": {
+        "mime-db": "1.52.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==",
+      "license": "ISC"
+    },
+    "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": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==",
+      "license": "MIT"
+    },
+    "node_modules/minimatch": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+      "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+      "license": "ISC",
+      "dependencies": {
+        "brace-expansion": "^1.1.7"
+      },
+      "engines": {
+        "node": "*"
+      }
+    },
+    "node_modules/minimist": {
+      "version": "1.2.8",
+      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+      "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/mkdirp": {
+      "version": "0.5.6",
+      "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
+      "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
+      "dependencies": {
+        "minimist": "^1.2.6"
+      },
+      "bin": {
+        "mkdirp": "bin/cmd.js"
+      }
+    },
+    "node_modules/mocha": {
+      "version": "10.2.0",
+      "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz",
+      "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==",
+      "license": "MIT",
+      "dependencies": {
+        "ansi-colors": "4.1.1",
+        "browser-stdout": "1.3.1",
+        "chokidar": "3.5.3",
+        "debug": "4.3.4",
+        "diff": "5.0.0",
+        "escape-string-regexp": "4.0.0",
+        "find-up": "5.0.0",
+        "glob": "7.2.0",
+        "he": "1.2.0",
+        "js-yaml": "4.1.0",
+        "log-symbols": "4.1.0",
+        "minimatch": "5.0.1",
+        "ms": "2.1.3",
+        "nanoid": "3.3.3",
+        "serialize-javascript": "6.0.0",
+        "strip-json-comments": "3.1.1",
+        "supports-color": "8.1.1",
+        "workerpool": "6.2.1",
+        "yargs": "16.2.0",
+        "yargs-parser": "20.2.4",
+        "yargs-unparser": "2.0.0"
+      },
+      "bin": {
+        "_mocha": "bin/_mocha",
+        "mocha": "bin/mocha.js"
+      },
+      "engines": {
+        "node": ">= 14.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/mochajs"
+      }
+    },
+    "node_modules/mocha/node_modules/brace-expansion": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+      "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+      "license": "MIT",
+      "dependencies": {
+        "balanced-match": "^1.0.0"
+      }
+    },
+    "node_modules/mocha/node_modules/glob": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
+      "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
+      "license": "ISC",
+      "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/mocha/node_modules/glob/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==",
+      "license": "MIT",
+      "dependencies": {
+        "balanced-match": "^1.0.0",
+        "concat-map": "0.0.1"
+      }
+    },
+    "node_modules/mocha/node_modules/glob/node_modules/minimatch": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+      "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+      "license": "ISC",
+      "dependencies": {
+        "brace-expansion": "^1.1.7"
+      },
+      "engines": {
+        "node": "*"
+      }
+    },
+    "node_modules/mocha/node_modules/minimatch": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz",
+      "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==",
+      "license": "ISC",
+      "dependencies": {
+        "brace-expansion": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+      "license": "MIT"
+    },
+    "node_modules/mustache": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz",
+      "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==",
+      "license": "MIT",
+      "bin": {
+        "mustache": "bin/mustache"
+      }
+    },
+    "node_modules/nan": {
+      "version": "2.17.0",
+      "resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz",
+      "integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==",
+      "license": "MIT"
+    },
+    "node_modules/nanoid": {
+      "version": "3.3.3",
+      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz",
+      "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==",
+      "license": "MIT",
+      "bin": {
+        "nanoid": "bin/nanoid.cjs"
+      },
+      "engines": {
+        "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+      }
+    },
+    "node_modules/near-api-js": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/near-api-js/-/near-api-js-1.1.0.tgz",
+      "integrity": "sha512-qYKv1mYsaDZc2uYndhS+ttDhR9+60qFc+ZjD6lWsAxr3ZskMjRwPffDGQZYhC7BRDQMe1HEbk6d5mf+TVm0Lqg==",
+      "license": "(MIT AND Apache-2.0)",
+      "dependencies": {
+        "bn.js": "5.2.1",
+        "borsh": "^0.7.0",
+        "bs58": "^4.0.0",
+        "depd": "^2.0.0",
+        "error-polyfill": "^0.1.3",
+        "http-errors": "^1.7.2",
+        "js-sha256": "^0.9.0",
+        "mustache": "^4.0.0",
+        "node-fetch": "^2.6.1",
+        "text-encoding-utf-8": "^1.0.2",
+        "tweetnacl": "^1.0.1"
+      }
+    },
+    "node_modules/no-case": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz",
+      "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==",
+      "license": "MIT",
+      "dependencies": {
+        "lower-case": "^2.0.2",
+        "tslib": "^2.0.3"
+      }
+    },
+    "node_modules/node-addon-api": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz",
+      "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==",
+      "license": "MIT"
+    },
+    "node_modules/node-fetch": {
+      "version": "2.6.9",
+      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz",
+      "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==",
+      "license": "MIT",
+      "dependencies": {
+        "whatwg-url": "^5.0.0"
+      },
+      "engines": {
+        "node": "4.x || >=6.0.0"
+      },
+      "peerDependencies": {
+        "encoding": "^0.1.0"
+      },
+      "peerDependenciesMeta": {
+        "encoding": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/node-gyp-build": {
+      "version": "4.6.0",
+      "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz",
+      "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==",
+      "license": "MIT",
+      "bin": {
+        "node-gyp-build": "bin.js",
+        "node-gyp-build-optional": "optional.js",
+        "node-gyp-build-test": "build-test.js"
+      }
+    },
+    "node_modules/noms": {
+      "version": "0.0.0",
+      "resolved": "https://registry.npmjs.org/noms/-/noms-0.0.0.tgz",
+      "integrity": "sha512-lNDU9VJaOPxUmXcLb+HQFeUgQQPtMI24Gt6hgfuMHRJgMRHMF/qZ4HJD3GDru4sSw9IQl2jPjAYnQrdIeLbwow==",
+      "license": "ISC",
+      "dependencies": {
+        "inherits": "^2.0.1",
+        "readable-stream": "~1.0.31"
+      }
+    },
+    "node_modules/noms/node_modules/readable-stream": {
+      "version": "1.0.34",
+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
+      "integrity": "sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==",
+      "license": "MIT",
+      "dependencies": {
+        "core-util-is": "~1.0.0",
+        "inherits": "~2.0.1",
+        "isarray": "0.0.1",
+        "string_decoder": "~0.10.x"
+      }
+    },
+    "node_modules/noms/node_modules/string_decoder": {
+      "version": "0.10.31",
+      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+      "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==",
+      "license": "MIT"
+    },
+    "node_modules/normalize-path": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+      "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/o3": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/o3/-/o3-1.0.3.tgz",
+      "integrity": "sha512-f+4n+vC6s4ysy7YO7O2gslWZBUu8Qj2i2OUJOvjRxQva7jVjYjB29jrr9NCjmxZQR0gzrOcv1RnqoYOeMs5VRQ==",
+      "license": "MIT",
+      "dependencies": {
+        "capability": "^0.2.5"
+      }
+    },
+    "node_modules/object-assign": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+      "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/object-keys": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
+      "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/once": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+      "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+      "license": "ISC",
+      "dependencies": {
+        "wrappy": "1"
+      }
+    },
+    "node_modules/optimism": {
+      "version": "0.16.2",
+      "resolved": "https://registry.npmjs.org/optimism/-/optimism-0.16.2.tgz",
+      "integrity": "sha512-zWNbgWj+3vLEjZNIh/okkY2EUfX+vB9TJopzIZwT1xxaMqC5hRLLraePod4c5n4He08xuXNH+zhKFFCu390wiQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@wry/context": "^0.7.0",
+        "@wry/trie": "^0.3.0"
+      }
+    },
+    "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==",
+      "license": "MIT",
+      "dependencies": {
+        "yocto-queue": "^0.1.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/p-locate": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+      "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+      "license": "MIT",
+      "dependencies": {
+        "p-limit": "^3.0.2"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/pako": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
+      "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==",
+      "license": "(MIT AND Zlib)"
+    },
+    "node_modules/path-exists": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+      "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "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": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+      "license": "MIT",
+      "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==",
+      "license": "MIT"
+    },
+    "node_modules/pathval": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz",
+      "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==",
+      "license": "MIT",
+      "engines": {
+        "node": "*"
+      }
+    },
+    "node_modules/pbkdf2": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz",
+      "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==",
+      "license": "MIT",
+      "dependencies": {
+        "create-hash": "^1.1.2",
+        "create-hmac": "^1.1.4",
+        "ripemd160": "^2.0.1",
+        "safe-buffer": "^5.0.1",
+        "sha.js": "^2.4.8"
+      },
+      "engines": {
+        "node": ">=0.12"
+      }
+    },
+    "node_modules/picomatch": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+      "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8.6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/jonschlinkert"
+      }
+    },
+    "node_modules/prettier": {
+      "version": "2.8.7",
+      "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.7.tgz",
+      "integrity": "sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw==",
+      "license": "MIT",
+      "bin": {
+        "prettier": "bin-prettier.js"
+      },
+      "engines": {
+        "node": ">=10.13.0"
+      },
+      "funding": {
+        "url": "https://github.com/prettier/prettier?sponsor=1"
+      }
+    },
+    "node_modules/process-nextick-args": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
+      "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
+      "license": "MIT"
+    },
+    "node_modules/prop-types": {
+      "version": "15.8.1",
+      "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
+      "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
+      "license": "MIT",
+      "dependencies": {
+        "loose-envify": "^1.4.0",
+        "object-assign": "^4.1.1",
+        "react-is": "^16.13.1"
+      }
+    },
+    "node_modules/protobufjs": {
+      "version": "6.11.3",
+      "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz",
+      "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==",
+      "hasInstallScript": true,
+      "license": "BSD-3-Clause",
+      "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/randombytes": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
+      "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+      "license": "MIT",
+      "dependencies": {
+        "safe-buffer": "^5.1.0"
+      }
+    },
+    "node_modules/react-is": {
+      "version": "16.13.1",
+      "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+      "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
+      "license": "MIT"
+    },
+    "node_modules/readable-stream": {
+      "version": "3.6.2",
+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+      "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+      "license": "MIT",
+      "dependencies": {
+        "inherits": "^2.0.3",
+        "string_decoder": "^1.1.1",
+        "util-deprecate": "^1.0.1"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/readdirp": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+      "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+      "license": "MIT",
+      "dependencies": {
+        "picomatch": "^2.2.1"
+      },
+      "engines": {
+        "node": ">=8.10.0"
+      }
+    },
+    "node_modules/readonly-date": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/readonly-date/-/readonly-date-1.0.0.tgz",
+      "integrity": "sha512-tMKIV7hlk0h4mO3JTmmVuIlJVXjKk3Sep9Bf5OH0O+758ruuVkUy2J9SttDLm91IEX/WHlXPSpxMGjPj4beMIQ==",
+      "license": "Apache-2.0"
+    },
+    "node_modules/rechoir": {
+      "version": "0.6.2",
+      "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz",
+      "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==",
+      "dependencies": {
+        "resolve": "^1.1.6"
+      },
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
+    "node_modules/regenerator-runtime": {
+      "version": "0.13.11",
+      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
+      "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
+      "license": "MIT"
+    },
+    "node_modules/require-directory": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+      "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/resolve": {
+      "version": "1.22.2",
+      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz",
+      "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==",
+      "license": "MIT",
+      "dependencies": {
+        "is-core-module": "^2.11.0",
+        "path-parse": "^1.0.7",
+        "supports-preserve-symlinks-flag": "^1.0.0"
+      },
+      "bin": {
+        "resolve": "bin/resolve"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/response-iterator": {
+      "version": "0.2.6",
+      "resolved": "https://registry.npmjs.org/response-iterator/-/response-iterator-0.2.6.tgz",
+      "integrity": "sha512-pVzEEzrsg23Sh053rmDUvLSkGXluZio0qu8VT6ukrYuvtjVfCbDZH9d6PGXb8HZfzdNZt8feXv/jvUzlhRgLnw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
+    "node_modules/rimraf": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+      "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+      "license": "ISC",
+      "dependencies": {
+        "glob": "^7.1.3"
+      },
+      "bin": {
+        "rimraf": "bin.js"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/ripemd160": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
+      "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==",
+      "license": "MIT",
+      "dependencies": {
+        "hash-base": "^3.0.0",
+        "inherits": "^2.0.1"
+      }
+    },
+    "node_modules/ripemd160-min": {
+      "version": "0.0.6",
+      "resolved": "https://registry.npmjs.org/ripemd160-min/-/ripemd160-min-0.0.6.tgz",
+      "integrity": "sha512-+GcJgQivhs6S9qvLogusiTcS9kQUfgR75whKuy5jIhuiOfQuJ8fjqxV6EGD5duH1Y/FawFUMtMhyeq3Fbnib8A==",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/rlp": {
+      "version": "2.2.7",
+      "resolved": "https://registry.npmjs.org/rlp/-/rlp-2.2.7.tgz",
+      "integrity": "sha512-d5gdPmgQ0Z+AklL2NVXr/IoSjNZFfTVvQWzL/AM2AOcSzYP2xjlb0AC8YyCLc41MSNf6P6QVtjgPdmVtzb+4lQ==",
+      "license": "MPL-2.0",
+      "dependencies": {
+        "bn.js": "^5.2.0"
+      },
+      "bin": {
+        "rlp": "bin/rlp"
+      }
+    },
+    "node_modules/rpc-websockets": {
+      "version": "7.5.1",
+      "resolved": "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-7.5.1.tgz",
+      "integrity": "sha512-kGFkeTsmd37pHPMaHIgN1LVKXMi0JD782v4Ds9ZKtLlwdTKjn+CxM9A9/gLT2LaOuEcEFGL98h1QWQtlOIdW0w==",
+      "license": "LGPL-3.0-only",
+      "dependencies": {
+        "@babel/runtime": "^7.17.2",
+        "eventemitter3": "^4.0.7",
+        "uuid": "^8.3.2",
+        "ws": "^8.5.0"
+      },
+      "funding": {
+        "type": "paypal",
+        "url": "https://paypal.me/kozjak"
+      },
+      "optionalDependencies": {
+        "bufferutil": "^4.0.1",
+        "utf-8-validate": "^5.0.2"
+      }
+    },
+    "node_modules/rpc-websockets/node_modules/ws": {
+      "version": "8.13.0",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
+      "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10.0.0"
+      },
+      "peerDependencies": {
+        "bufferutil": "^4.0.1",
+        "utf-8-validate": ">=5.0.2"
+      },
+      "peerDependenciesMeta": {
+        "bufferutil": {
+          "optional": true
+        },
+        "utf-8-validate": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/rxjs": {
+      "version": "7.8.0",
+      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz",
+      "integrity": "sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "tslib": "^2.1.0"
+      }
+    },
+    "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==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "MIT"
+    },
+    "node_modules/scrypt-js": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz",
+      "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==",
+      "license": "MIT"
+    },
+    "node_modules/secp256k1": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.3.tgz",
+      "integrity": "sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA==",
+      "hasInstallScript": true,
+      "license": "MIT",
+      "dependencies": {
+        "elliptic": "^6.5.4",
+        "node-addon-api": "^2.0.0",
+        "node-gyp-build": "^4.2.0"
+      },
+      "engines": {
+        "node": ">=10.0.0"
+      }
+    },
+    "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==",
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "randombytes": "^2.1.0"
+      }
+    },
+    "node_modules/setimmediate": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
+      "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
+      "license": "MIT"
+    },
+    "node_modules/setprototypeof": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+      "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+      "license": "ISC"
+    },
+    "node_modules/sha.js": {
+      "version": "2.4.11",
+      "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
+      "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
+      "license": "(MIT AND BSD-3-Clause)",
+      "dependencies": {
+        "inherits": "^2.0.1",
+        "safe-buffer": "^5.0.1"
+      },
+      "bin": {
+        "sha.js": "bin.js"
+      }
+    },
+    "node_modules/sha3": {
+      "version": "2.1.4",
+      "resolved": "https://registry.npmjs.org/sha3/-/sha3-2.1.4.tgz",
+      "integrity": "sha512-S8cNxbyb0UGUM2VhRD4Poe5N58gJnJsLJ5vC7FYWGUmGhcsj4++WaIOBFVDxlG0W3To6xBuiRh+i0Qp2oNCOtg==",
+      "license": "MIT",
+      "dependencies": {
+        "buffer": "6.0.3"
+      }
+    },
+    "node_modules/shelljs": {
+      "version": "0.8.5",
+      "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz",
+      "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==",
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "glob": "^7.0.0",
+        "interpret": "^1.0.0",
+        "rechoir": "^0.6.2"
+      },
+      "bin": {
+        "shjs": "bin/shjs"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/shx": {
+      "version": "0.3.4",
+      "resolved": "https://registry.npmjs.org/shx/-/shx-0.3.4.tgz",
+      "integrity": "sha512-N6A9MLVqjxZYcVn8hLmtneQWIJtp8IKzMP4eMnx+nqkvXoqinUPCbUFLp2UcWTEIUONhlk0ewxr/jaVGlc+J+g==",
+      "license": "MIT",
+      "dependencies": {
+        "minimist": "^1.2.3",
+        "shelljs": "^0.8.5"
+      },
+      "bin": {
+        "shx": "lib/cli.js"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/snake-case": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz",
+      "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==",
+      "license": "MIT",
+      "dependencies": {
+        "dot-case": "^3.0.4",
+        "tslib": "^2.0.3"
+      }
+    },
+    "node_modules/snakecase-keys": {
+      "version": "5.4.5",
+      "resolved": "https://registry.npmjs.org/snakecase-keys/-/snakecase-keys-5.4.5.tgz",
+      "integrity": "sha512-qSQVcgcWk8mQUN1miVGnRMAUye1dbj9+F9PVkR7wZUXNCidQwrl/kOKmoYf+WbH2ju6c9pXnlmbS2he7pb2/9A==",
+      "license": "MIT",
+      "dependencies": {
+        "map-obj": "^4.1.0",
+        "snake-case": "^3.0.4",
+        "type-fest": "^2.5.2"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "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==",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/source-map-support": {
+      "version": "0.5.21",
+      "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
+      "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
+      "dependencies": {
+        "buffer-from": "^1.0.0",
+        "source-map": "^0.6.0"
+      }
+    },
+    "node_modules/statuses": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
+      "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/store2": {
+      "version": "2.14.2",
+      "resolved": "https://registry.npmjs.org/store2/-/store2-2.14.2.tgz",
+      "integrity": "sha512-siT1RiqlfQnGqgT/YzXVUNsom9S0H1OX+dpdGN1xkyYATo4I6sep5NmsRD/40s3IIOvlCq6akxkqG82urIZW1w==",
+      "license": "(MIT OR GPL-3.0)"
+    },
+    "node_modules/string_decoder": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+      "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+      "license": "MIT",
+      "dependencies": {
+        "safe-buffer": "~5.2.0"
+      }
+    },
+    "node_modules/string-width": {
+      "version": "4.2.3",
+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+      "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+      "license": "MIT",
+      "dependencies": {
+        "emoji-regex": "^8.0.0",
+        "is-fullwidth-code-point": "^3.0.0",
+        "strip-ansi": "^6.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/strip-ansi": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+      "license": "MIT",
+      "dependencies": {
+        "ansi-regex": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/strip-bom": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+      "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
+      "license": "MIT",
+      "optional": true,
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/strip-hex-prefix": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz",
+      "integrity": "sha512-q8d4ue7JGEiVcypji1bALTos+0pWtyGlivAWyPuTkHzuTCJqrK9sWxYQZUq6Nq3cuyv3bm734IhHvHtGGURU6A==",
+      "license": "MIT",
+      "dependencies": {
+        "is-hex-prefixed": "1.0.0"
+      },
+      "engines": {
+        "node": ">=6.5.0",
+        "npm": ">=3"
+      }
+    },
+    "node_modules/strip-json-comments": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+      "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/superstruct": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-1.0.3.tgz",
+      "integrity": "sha512-8iTn3oSS8nRGn+C2pgXSKPI3jmpm6FExNazNpjvqS6ZUJQCej3PUXEKM8NjHBOs54ExM+LPW/FBRhymrdcCiSg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=14.0.0"
+      }
+    },
+    "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==",
+      "license": "MIT",
+      "dependencies": {
+        "has-flag": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/supports-color?sponsor=1"
+      }
+    },
+    "node_modules/supports-preserve-symlinks-flag": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+      "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/symbol-observable": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz",
+      "integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10"
+      }
+    },
+    "node_modules/text-encoding-utf-8": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz",
+      "integrity": "sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg=="
+    },
+    "node_modules/through": {
+      "version": "2.3.8",
+      "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+      "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==",
+      "license": "MIT"
+    },
+    "node_modules/through2": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
+      "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==",
+      "license": "MIT",
+      "dependencies": {
+        "readable-stream": "~2.3.6",
+        "xtend": "~4.0.1"
+      }
+    },
+    "node_modules/through2/node_modules/isarray": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+      "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
+      "license": "MIT"
+    },
+    "node_modules/through2/node_modules/readable-stream": {
+      "version": "2.3.8",
+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
+      "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
+      "license": "MIT",
+      "dependencies": {
+        "core-util-is": "~1.0.0",
+        "inherits": "~2.0.3",
+        "isarray": "~1.0.0",
+        "process-nextick-args": "~2.0.0",
+        "safe-buffer": "~5.1.1",
+        "string_decoder": "~1.1.1",
+        "util-deprecate": "~1.0.1"
+      }
+    },
+    "node_modules/through2/node_modules/safe-buffer": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+      "license": "MIT"
+    },
+    "node_modules/through2/node_modules/string_decoder": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+      "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+      "license": "MIT",
+      "dependencies": {
+        "safe-buffer": "~5.1.0"
+      }
+    },
+    "node_modules/tiny-secp256k1": {
+      "version": "1.1.6",
+      "resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-1.1.6.tgz",
+      "integrity": "sha512-FmqJZGduTyvsr2cF3375fqGHUovSwDi/QytexX1Se4BPuPZpTE5Ftp5fg+EFSuEf3lhZqgCRjEG3ydUQ/aNiwA==",
+      "hasInstallScript": true,
+      "license": "MIT",
+      "dependencies": {
+        "bindings": "^1.3.0",
+        "bn.js": "^4.11.8",
+        "create-hmac": "^1.1.7",
+        "elliptic": "^6.4.0",
+        "nan": "^2.13.2"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/tiny-secp256k1/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==",
+      "license": "MIT"
+    },
+    "node_modules/tmp": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz",
+      "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==",
+      "license": "MIT",
+      "dependencies": {
+        "rimraf": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=8.17.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==",
+      "license": "MIT",
+      "dependencies": {
+        "is-number": "^7.0.0"
+      },
+      "engines": {
+        "node": ">=8.0"
+      }
+    },
+    "node_modules/toidentifier": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+      "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.6"
+      }
+    },
+    "node_modules/toml": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/toml/-/toml-3.0.0.tgz",
+      "integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==",
+      "license": "MIT"
+    },
+    "node_modules/tr46": {
+      "version": "0.0.3",
+      "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+      "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
+      "license": "MIT"
+    },
+    "node_modules/ts-invariant": {
+      "version": "0.10.3",
+      "resolved": "https://registry.npmjs.org/ts-invariant/-/ts-invariant-0.10.3.tgz",
+      "integrity": "sha512-uivwYcQaxAucv1CzRp2n/QdYPo4ILf9VXgH19zEIjFx2EJufV16P0JtJVpYHy89DItG6Kwj2oIUjrcK5au+4tQ==",
+      "license": "MIT",
+      "dependencies": {
+        "tslib": "^2.1.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/ts-mocha": {
+      "version": "10.0.0",
+      "resolved": "https://registry.npmjs.org/ts-mocha/-/ts-mocha-10.0.0.tgz",
+      "integrity": "sha512-VRfgDO+iiuJFlNB18tzOfypJ21xn2xbuZyDvJvqpTbWgkAgD17ONGr8t+Tl8rcBtOBdjXp5e/Rk+d39f7XBHRw==",
+      "license": "MIT",
+      "dependencies": {
+        "ts-node": "7.0.1"
+      },
+      "bin": {
+        "ts-mocha": "bin/ts-mocha"
+      },
+      "engines": {
+        "node": ">= 6.X.X"
+      },
+      "optionalDependencies": {
+        "tsconfig-paths": "^3.5.0"
+      },
+      "peerDependencies": {
+        "mocha": "^3.X.X || ^4.X.X || ^5.X.X || ^6.X.X || ^7.X.X || ^8.X.X || ^9.X.X || ^10.X.X"
+      }
+    },
+    "node_modules/ts-mocha/node_modules/diff": {
+      "version": "3.5.0",
+      "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
+      "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==",
+      "engines": {
+        "node": ">=0.3.1"
+      }
+    },
+    "node_modules/ts-mocha/node_modules/ts-node": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-7.0.1.tgz",
+      "integrity": "sha512-BVwVbPJRspzNh2yfslyT1PSbl5uIk03EZlb493RKHN4qej/D06n1cEhjlOJG69oFsE7OT8XjpTUcYf6pKTLMhw==",
+      "dependencies": {
+        "arrify": "^1.0.0",
+        "buffer-from": "^1.1.0",
+        "diff": "^3.1.0",
+        "make-error": "^1.1.1",
+        "minimist": "^1.2.0",
+        "mkdirp": "^0.5.1",
+        "source-map-support": "^0.5.6",
+        "yn": "^2.0.0"
+      },
+      "bin": {
+        "ts-node": "dist/bin.js"
+      },
+      "engines": {
+        "node": ">=4.2.0"
+      }
+    },
+    "node_modules/ts-mocha/node_modules/yn": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz",
+      "integrity": "sha512-uTv8J/wiWTgUTg+9vLTi//leUl5vDQS6uii/emeTb2ssY7vl6QWf2fFbIIGjnhjvbdKlU0ed7QPgY1htTC86jQ==",
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/ts-node": {
+      "version": "10.9.1",
+      "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
+      "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==",
+      "dependencies": {
+        "@cspotcode/source-map-support": "^0.8.0",
+        "@tsconfig/node10": "^1.0.7",
+        "@tsconfig/node12": "^1.0.7",
+        "@tsconfig/node14": "^1.0.0",
+        "@tsconfig/node16": "^1.0.2",
+        "acorn": "^8.4.1",
+        "acorn-walk": "^8.1.1",
+        "arg": "^4.1.0",
+        "create-require": "^1.1.0",
+        "diff": "^4.0.1",
+        "make-error": "^1.1.1",
+        "v8-compile-cache-lib": "^3.0.1",
+        "yn": "3.1.1"
+      },
+      "bin": {
+        "ts-node": "dist/bin.js",
+        "ts-node-cwd": "dist/bin-cwd.js",
+        "ts-node-esm": "dist/bin-esm.js",
+        "ts-node-script": "dist/bin-script.js",
+        "ts-node-transpile-only": "dist/bin-transpile.js",
+        "ts-script": "dist/bin-script-deprecated.js"
+      },
+      "peerDependencies": {
+        "@swc/core": ">=1.2.50",
+        "@swc/wasm": ">=1.2.50",
+        "@types/node": "*",
+        "typescript": ">=2.7"
+      },
+      "peerDependenciesMeta": {
+        "@swc/core": {
+          "optional": true
+        },
+        "@swc/wasm": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/ts-node/node_modules/acorn": {
+      "version": "8.8.2",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
+      "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==",
+      "bin": {
+        "acorn": "bin/acorn"
+      },
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
+    "node_modules/ts-node/node_modules/diff": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
+      "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
+      "engines": {
+        "node": ">=0.3.1"
+      }
+    },
+    "node_modules/tsconfig-paths": {
+      "version": "3.14.2",
+      "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz",
+      "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "@types/json5": "^0.0.29",
+        "json5": "^1.0.2",
+        "minimist": "^1.2.6",
+        "strip-bom": "^3.0.0"
+      }
+    },
+    "node_modules/tslib": {
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz",
+      "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==",
+      "license": "0BSD"
+    },
+    "node_modules/tweetnacl": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz",
+      "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==",
+      "license": "Unlicense"
+    },
+    "node_modules/tweetnacl-util": {
+      "version": "0.15.1",
+      "resolved": "https://registry.npmjs.org/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz",
+      "integrity": "sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw==",
+      "license": "Unlicense"
+    },
+    "node_modules/type-detect": {
+      "version": "4.0.8",
+      "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
+      "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/type-fest": {
+      "version": "2.19.0",
+      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz",
+      "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==",
+      "license": "(MIT OR CC0-1.0)",
+      "engines": {
+        "node": ">=12.20"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/typeforce": {
+      "version": "1.18.0",
+      "resolved": "https://registry.npmjs.org/typeforce/-/typeforce-1.18.0.tgz",
+      "integrity": "sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==",
+      "license": "MIT"
+    },
+    "node_modules/typescript": {
+      "version": "5.0.4",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz",
+      "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==",
+      "license": "Apache-2.0",
+      "bin": {
+        "tsc": "bin/tsc",
+        "tsserver": "bin/tsserver"
+      },
+      "engines": {
+        "node": ">=12.20"
+      }
+    },
+    "node_modules/u3": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/u3/-/u3-0.1.1.tgz",
+      "integrity": "sha512-+J5D5ir763y+Am/QY6hXNRlwljIeRMZMGs0cT6qqZVVzzT3X3nFPXVyPOFRMOR4kupB0T8JnCdpWdp6Q/iXn3w==",
+      "license": "MIT"
+    },
+    "node_modules/untildify": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz",
+      "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/utf-8-validate": {
+      "version": "5.0.10",
+      "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz",
+      "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==",
+      "hasInstallScript": true,
+      "license": "MIT",
+      "dependencies": {
+        "node-gyp-build": "^4.3.0"
+      },
+      "engines": {
+        "node": ">=6.14.2"
+      }
+    },
+    "node_modules/util-deprecate": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+      "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+      "license": "MIT"
+    },
+    "node_modules/uuid": {
+      "version": "8.3.2",
+      "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+      "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
+      "license": "MIT",
+      "bin": {
+        "uuid": "dist/bin/uuid"
+      }
+    },
+    "node_modules/v8-compile-cache-lib": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
+      "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg=="
+    },
+    "node_modules/vlq": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/vlq/-/vlq-2.0.4.tgz",
+      "integrity": "sha512-aodjPa2wPQFkra1G8CzJBTHXhgk3EVSwxSWXNPr1fgdFLUb8kvLV1iEb6rFgasIsjP82HWI6dsb5Io26DDnasA==",
+      "license": "MIT"
+    },
+    "node_modules/webidl-conversions": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+      "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
+      "license": "BSD-2-Clause"
+    },
+    "node_modules/whatwg-url": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+      "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+      "license": "MIT",
+      "dependencies": {
+        "tr46": "~0.0.3",
+        "webidl-conversions": "^3.0.0"
+      }
+    },
+    "node_modules/wif": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/wif/-/wif-2.0.6.tgz",
+      "integrity": "sha512-HIanZn1zmduSF+BQhkE+YXIbEiH0xPr1012QbFEGB0xsKqJii0/SqJjyn8dFv6y36kOznMgMB+LGcbZTJ1xACQ==",
+      "license": "MIT",
+      "dependencies": {
+        "bs58check": "<3.0.0"
+      }
+    },
+    "node_modules/workerpool": {
+      "version": "6.2.1",
+      "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz",
+      "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==",
+      "license": "Apache-2.0"
+    },
+    "node_modules/wrap-ansi": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+      "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+      "license": "MIT",
+      "dependencies": {
+        "ansi-styles": "^4.0.0",
+        "string-width": "^4.1.0",
+        "strip-ansi": "^6.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+      }
+    },
+    "node_modules/wrappy": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+      "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+      "license": "ISC"
+    },
+    "node_modules/ws": {
+      "version": "7.5.9",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
+      "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
+      "license": "MIT",
+      "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/xstream": {
+      "version": "11.14.0",
+      "resolved": "https://registry.npmjs.org/xstream/-/xstream-11.14.0.tgz",
+      "integrity": "sha512-1bLb+kKKtKPbgTK6i/BaoAn03g47PpFstlbe1BA+y3pNS/LfvcaghS5BFf9+EE1J+KwSQsEpfJvFN5GqFtiNmw==",
+      "license": "MIT",
+      "dependencies": {
+        "globalthis": "^1.0.1",
+        "symbol-observable": "^2.0.3"
+      }
+    },
+    "node_modules/xstream/node_modules/symbol-observable": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-2.0.3.tgz",
+      "integrity": "sha512-sQV7phh2WCYAn81oAkakC5qjq2Ml0g8ozqz03wOGnx9dDlG1de6yrF+0RAzSJD8fPUow3PTSMf2SAbOGxb93BA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10"
+      }
+    },
+    "node_modules/xtend": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+      "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.4"
+      }
+    },
+    "node_modules/y18n": {
+      "version": "5.0.8",
+      "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+      "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+      "license": "ISC",
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/yargs": {
+      "version": "16.2.0",
+      "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
+      "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
+      "license": "MIT",
+      "dependencies": {
+        "cliui": "^7.0.2",
+        "escalade": "^3.1.1",
+        "get-caller-file": "^2.0.5",
+        "require-directory": "^2.1.1",
+        "string-width": "^4.2.0",
+        "y18n": "^5.0.5",
+        "yargs-parser": "^20.2.2"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/yargs-parser": {
+      "version": "20.2.4",
+      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz",
+      "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==",
+      "license": "ISC",
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/yargs-unparser": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz",
+      "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==",
+      "license": "MIT",
+      "dependencies": {
+        "camelcase": "^6.0.0",
+        "decamelize": "^4.0.0",
+        "flat": "^5.0.2",
+        "is-plain-obj": "^2.1.0"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/yargs-unparser/node_modules/camelcase": {
+      "version": "6.3.0",
+      "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
+      "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/yargs/node_modules/yargs-parser": {
+      "version": "20.2.9",
+      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
+      "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
+      "license": "ISC",
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/yn": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
+      "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "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==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/zen-observable": {
+      "version": "0.8.15",
+      "resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz",
+      "integrity": "sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==",
+      "license": "MIT"
+    },
+    "node_modules/zen-observable-ts": {
+      "version": "1.2.5",
+      "resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-1.2.5.tgz",
+      "integrity": "sha512-QZWQekv6iB72Naeake9hS1KxHlotfRpe+WGNbNx5/ta+R3DNjVO2bswf63gXlWDcs+EMd7XY8HfVQyP1X6T4Zg==",
+      "license": "MIT",
+      "dependencies": {
+        "zen-observable": "0.8.15"
+      }
+    }
+  }
+}

+ 22 - 0
target_chains/sui/vendor/wormhole_movement_testnet/testing/package.json

@@ -0,0 +1,22 @@
+{
+  "name": "@wormhole-foundation/wormhole-sui-integration-test",
+  "version": "0.0.1",
+  "description": "Wormhole Sui Integration Test",
+  "main": "index.js",
+  "license": "MIT",
+  "dependencies": {
+    "@certusone/wormhole-sdk": "^0.9.12",
+    "@mysten/sui.js": "^0.32.2",
+    "chai": "^4.3.7",
+    "mocha": "^10.2.0",
+    "prettier": "^2.8.7",
+    "ts-mocha": "^10.0.0",
+    "ts-node": "^10.9.1",
+    "typescript": "^5.0.4"
+  },
+  "devDependencies": {
+    "@types/chai": "^4.3.4",
+    "@types/mocha": "^10.0.1",
+    "@types/node": "^18.15.11"
+  }
+}

+ 35 - 0
target_chains/sui/vendor/wormhole_movement_testnet/testing/run_integration_test.sh

@@ -0,0 +1,35 @@
+#/bin/bash
+
+pgrep -f sui > /dev/null
+if [ $? -eq 0 ]; then
+    echo "sui local validator already running"
+    exit 1;
+fi
+
+TEST_DIR=$(dirname $0)
+SUI_CONFIG=$TEST_DIR/sui_config
+
+### Remove databases generated by localnet
+rm -rf $SUI_CONFIG/*_db
+
+### Start local node
+echo "$(date) :: starting localnet"
+sui start --network.config $SUI_CONFIG/network.yaml > /dev/null 2>&1 &
+sleep 1
+
+echo "$(date) :: deploying wormhole and token bridge"
+cd $TEST_DIR/..
+bash scripts/deploy.sh devnet \
+    -k AGA20wtGcwbcNAG4nwapbQ5wIuXwkYQEWFUoSVAxctHb > deploy.out 2>&1
+cd testing
+
+## run contract tests here
+echo "$(date) :: running tests"
+npx ts-mocha -t 1000000 $TEST_DIR/js/*.ts
+
+# nuke
+echo "$(date) :: done"
+pkill sui
+
+# remove databases generated by localnet
+rm -rf $SUI_CONFIG/*_db

+ 300 - 0
target_chains/sui/vendor/wormhole_movement_testnet/testing/scripts/upgrade-token-bridge.ts

@@ -0,0 +1,300 @@
+import * as mock from "@certusone/wormhole-sdk/lib/cjs/mock";
+import {
+  RawSigner,
+  SUI_CLOCK_OBJECT_ID,
+  TransactionBlock,
+  fromB64,
+  normalizeSuiObjectId,
+  JsonRpcProvider,
+  Ed25519Keypair,
+  testnetConnection,
+} from "@mysten/sui.js";
+import { execSync } from "child_process";
+import { resolve } from "path";
+import * as fs from "fs";
+
+const GOVERNANCE_EMITTER =
+  "0000000000000000000000000000000000000000000000000000000000000004";
+
+const TOKEN_BRIDGE_STATE_ID =
+  "0x32422cb2f929b6a4e3f81b4791ea11ac2af896b310f3d9442aa1fe924ce0bab4";
+const WORMHOLE_STATE_ID =
+  "0x69ae41bdef4770895eb4e7aaefee5e4673acc08f6917b4856cf55549c4573ca8";
+
+async function main() {
+  const guardianPrivateKey = process.env.TESTNET_GUARDIAN_PRIVATE_KEY;
+  if (guardianPrivateKey === undefined) {
+    throw new Error("TESTNET_GUARDIAN_PRIVATE_KEY unset in environment");
+  }
+
+  const walletPrivateKey = process.env.TESTNET_WALLET_PRIVATE_KEY;
+  if (walletPrivateKey === undefined) {
+    throw new Error("TESTNET_WALLET_PRIVATE_KEY unset in environment");
+  }
+
+  const provider = new JsonRpcProvider(testnetConnection);
+  const wallet = new RawSigner(
+    Ed25519Keypair.fromSecretKey(
+      Buffer.from(walletPrivateKey, "base64").subarray(1)
+    ),
+    provider
+  );
+
+  const dstTokenBridgePath = resolve(`${__dirname}/../../token_bridge`);
+
+  // Build for digest.
+  const { modules, dependencies, digest } =
+    buildForBytecodeAndDigest(dstTokenBridgePath);
+  console.log("dependencies", dependencies);
+  console.log("digest", digest.toString("hex"));
+
+  // We will use the signed VAA when we execute the upgrade.
+  const guardians = new mock.MockGuardians(0, [guardianPrivateKey]);
+
+  const timestamp = 12345678;
+  const governance = new mock.GovernanceEmitter(GOVERNANCE_EMITTER);
+  const published = governance.publishWormholeUpgradeContract(
+    timestamp,
+    2,
+    "0x" + digest.toString("hex")
+  );
+  const moduleName = Buffer.alloc(32);
+  moduleName.write("TokenBridge", 32 - "TokenBridge".length);
+  published.write(moduleName.toString(), 84 - 33);
+  published.writeUInt16BE(21, 84);
+  published.writeUInt8(2, 83);
+  //message.writeUInt8(1, 83);
+  published.writeUInt16BE(21, published.length - 34);
+
+  const signedVaa = guardians.addSignatures(published, [0]);
+  console.log("Upgrade VAA:", signedVaa.toString("hex"));
+
+  // // And execute upgrade with governance VAA.
+  // const upgradeResults = await upgradeTokenBridge(
+  //   wallet,
+  //   TOKEN_BRIDGE_STATE_ID,
+  //   WORMHOLE_STATE_ID,
+  //   modules,
+  //   dependencies,
+  //   signedVaa
+  // );
+
+  // console.log("tx digest", upgradeResults.digest);
+  // console.log("tx effects", JSON.stringify(upgradeResults.effects!));
+  // console.log("tx events", JSON.stringify(upgradeResults.events!));
+
+  // TODO: grab new package ID from the events above. Do not rely on the RPC
+  // call because it may give you a stale package ID after the upgrade.
+
+  const migrateResults = await migrateTokenBridge(
+    wallet,
+    TOKEN_BRIDGE_STATE_ID,
+    WORMHOLE_STATE_ID,
+    signedVaa
+  );
+  console.log("tx digest", migrateResults.digest);
+  console.log("tx effects", JSON.stringify(migrateResults.effects!));
+  console.log("tx events", JSON.stringify(migrateResults.events!));
+}
+
+main();
+
+// Yeah buddy.
+
+function buildForBytecodeAndDigest(packagePath: string) {
+  const buildOutput: {
+    modules: string[];
+    dependencies: string[];
+    digest: number[];
+  } = JSON.parse(
+    execSync(
+      `sui move build --dump-bytecode-as-base64 -p ${packagePath} 2> /dev/null`,
+      { encoding: "utf-8" }
+    )
+  );
+  return {
+    modules: buildOutput.modules.map((m: string) => Array.from(fromB64(m))),
+    dependencies: buildOutput.dependencies.map((d: string) =>
+      normalizeSuiObjectId(d)
+    ),
+    digest: Buffer.from(buildOutput.digest),
+  };
+}
+
+async function getPackageId(
+  provider: JsonRpcProvider,
+  stateId: string
+): Promise<string> {
+  const state = await provider
+    .getObject({
+      id: stateId,
+      options: {
+        showContent: true,
+      },
+    })
+    .then((result) => {
+      if (result.data?.content?.dataType == "moveObject") {
+        return result.data.content.fields;
+      }
+
+      throw new Error("not move object");
+    });
+
+  if ("upgrade_cap" in state) {
+    return state.upgrade_cap.fields.package;
+  }
+
+  throw new Error("upgrade_cap not found");
+}
+
+async function upgradeTokenBridge(
+  signer: RawSigner,
+  tokenBridgeStateId: string,
+  wormholeStateId: string,
+  modules: number[][],
+  dependencies: string[],
+  signedVaa: Buffer
+) {
+  const tokenBridgePackage = await getPackageId(
+    signer.provider,
+    tokenBridgeStateId
+  );
+  const wormholePackage = await getPackageId(signer.provider, wormholeStateId);
+
+  const tx = new TransactionBlock();
+
+  const [verifiedVaa] = tx.moveCall({
+    target: `${wormholePackage}::vaa::parse_and_verify`,
+    arguments: [
+      tx.object(wormholeStateId),
+      tx.pure(Array.from(signedVaa)),
+      tx.object(SUI_CLOCK_OBJECT_ID),
+    ],
+  });
+  const [decreeTicket] = tx.moveCall({
+    target: `${tokenBridgePackage}::upgrade_contract::authorize_governance`,
+    arguments: [tx.object(tokenBridgeStateId)],
+  });
+  const [decreeReceipt] = tx.moveCall({
+    target: `${wormholePackage}::governance_message::verify_vaa`,
+    arguments: [tx.object(wormholeStateId), verifiedVaa, decreeTicket],
+    typeArguments: [
+      `${tokenBridgePackage}::upgrade_contract::GovernanceWitness`,
+    ],
+  });
+
+  // Authorize upgrade.
+  const [upgradeTicket] = tx.moveCall({
+    target: `${tokenBridgePackage}::upgrade_contract::authorize_upgrade`,
+    arguments: [tx.object(tokenBridgeStateId), decreeReceipt],
+  });
+
+  // Build and generate modules and dependencies for upgrade.
+  const [upgradeReceipt] = tx.upgrade({
+    modules,
+    dependencies,
+    packageId: tokenBridgePackage,
+    ticket: upgradeTicket,
+  });
+
+  // Commit upgrade.
+  tx.moveCall({
+    target: `${tokenBridgePackage}::upgrade_contract::commit_upgrade`,
+    arguments: [tx.object(tokenBridgeStateId), upgradeReceipt],
+  });
+
+  // Cannot auto compute gas budget, so we need to configure it manually.
+  // Gas ~215m.
+  //tx.setGasBudget(1_000_000_000n);
+
+  return signer.signAndExecuteTransactionBlock({
+    transactionBlock: tx,
+    options: {
+      showEffects: true,
+      showEvents: true,
+    },
+  });
+}
+
+async function migrateTokenBridge(
+  signer: RawSigner,
+  tokenBridgeStateId: string,
+  wormholeStateId: string,
+  signedUpgradeVaa: Buffer
+) {
+  const tokenBridgePackage = await getPackageId(
+    signer.provider,
+    tokenBridgeStateId
+  );
+  const wormholePackage = await getPackageId(signer.provider, wormholeStateId);
+
+  const tx = new TransactionBlock();
+
+  const [verifiedVaa] = tx.moveCall({
+    target: `${wormholePackage}::vaa::parse_and_verify`,
+    arguments: [
+      tx.object(wormholeStateId),
+      tx.pure(Array.from(signedUpgradeVaa)),
+      tx.object(SUI_CLOCK_OBJECT_ID),
+    ],
+  });
+  const [decreeTicket] = tx.moveCall({
+    target: `${tokenBridgePackage}::upgrade_contract::authorize_governance`,
+    arguments: [tx.object(tokenBridgeStateId)],
+  });
+  const [decreeReceipt] = tx.moveCall({
+    target: `${wormholePackage}::governance_message::verify_vaa`,
+    arguments: [tx.object(wormholeStateId), verifiedVaa, decreeTicket],
+    typeArguments: [
+      `${tokenBridgePackage}::upgrade_contract::GovernanceWitness`,
+    ],
+  });
+  tx.moveCall({
+    target: `${tokenBridgePackage}::migrate::migrate`,
+    arguments: [tx.object(tokenBridgeStateId), decreeReceipt],
+  });
+
+  return signer.signAndExecuteTransactionBlock({
+    transactionBlock: tx,
+    options: {
+      showEffects: true,
+      showEvents: true,
+    },
+  });
+}
+
+function setUpWormholeDirectory(
+  srcWormholePath: string,
+  dstWormholePath: string
+) {
+  fs.cpSync(srcWormholePath, dstWormholePath, { recursive: true });
+
+  // Remove irrelevant files. This part is not necessary, but is helpful
+  // for debugging a clean package directory.
+  const removeThese = [
+    "Move.devnet.toml",
+    "Move.lock",
+    "Makefile",
+    "README.md",
+    "build",
+  ];
+  for (const basename of removeThese) {
+    fs.rmSync(`${dstWormholePath}/${basename}`, {
+      recursive: true,
+      force: true,
+    });
+  }
+
+  // Fix Move.toml file.
+  const moveTomlPath = `${dstWormholePath}/Move.toml`;
+  const moveToml = fs.readFileSync(moveTomlPath, "utf-8");
+  fs.writeFileSync(
+    moveTomlPath,
+    moveToml.replace(`wormhole = "_"`, `wormhole = "0x0"`),
+    "utf-8"
+  );
+}
+
+function cleanUpPackageDirectory(packagePath: string) {
+  fs.rmSync(packagePath, { recursive: true, force: true });
+}

+ 267 - 0
target_chains/sui/vendor/wormhole_movement_testnet/testing/scripts/upgrade-wormhole.ts

@@ -0,0 +1,267 @@
+import * as mock from "@certusone/wormhole-sdk/lib/cjs/mock";
+import {
+  RawSigner,
+  SUI_CLOCK_OBJECT_ID,
+  TransactionBlock,
+  fromB64,
+  normalizeSuiObjectId,
+  JsonRpcProvider,
+  Ed25519Keypair,
+  testnetConnection,
+} from "@mysten/sui.js";
+import { execSync } from "child_process";
+import { resolve } from "path";
+import * as fs from "fs";
+
+const GOVERNANCE_EMITTER =
+  "0000000000000000000000000000000000000000000000000000000000000004";
+
+const WORMHOLE_STATE_ID =
+  "0x69ae41bdef4770895eb4e7aaefee5e4673acc08f6917b4856cf55549c4573ca8";
+
+async function main() {
+  const guardianPrivateKey = process.env.TESTNET_GUARDIAN_PRIVATE_KEY;
+  if (guardianPrivateKey === undefined) {
+    throw new Error("TESTNET_GUARDIAN_PRIVATE_KEY unset in environment");
+  }
+
+  const walletPrivateKey = process.env.TESTNET_WALLET_PRIVATE_KEY;
+  if (walletPrivateKey === undefined) {
+    throw new Error("TESTNET_WALLET_PRIVATE_KEY unset in environment");
+  }
+
+  const provider = new JsonRpcProvider(testnetConnection);
+  const wallet = new RawSigner(
+    Ed25519Keypair.fromSecretKey(
+      Buffer.from(walletPrivateKey, "base64").subarray(1)
+    ),
+    provider
+  );
+
+  const srcWormholePath = resolve(`${__dirname}/../../wormhole`);
+  const dstWormholePath = resolve(`${__dirname}/wormhole`);
+
+  // Stage build(s).
+  setUpWormholeDirectory(srcWormholePath, dstWormholePath);
+
+  // Build for digest.
+  const { modules, dependencies, digest } =
+    buildForBytecodeAndDigest(dstWormholePath);
+
+  // We will use the signed VAA when we execute the upgrade.
+  const guardians = new mock.MockGuardians(0, [guardianPrivateKey]);
+
+  const timestamp = 12345678;
+  const governance = new mock.GovernanceEmitter(GOVERNANCE_EMITTER);
+  const published = governance.publishWormholeUpgradeContract(
+    timestamp,
+    2,
+    "0x" + digest.toString("hex")
+  );
+  published.writeUInt16BE(21, published.length - 34);
+
+  const signedVaa = guardians.addSignatures(published, [0]);
+  console.log("Upgrade VAA:", signedVaa.toString("hex"));
+
+  // And execute upgrade with governance VAA.
+  const upgradeResults = await buildAndUpgradeWormhole(
+    wallet,
+    WORMHOLE_STATE_ID,
+    modules,
+    dependencies,
+    signedVaa
+  );
+
+  console.log("tx digest", upgradeResults.digest);
+  console.log("tx effects", JSON.stringify(upgradeResults.effects!));
+  console.log("tx events", JSON.stringify(upgradeResults.events!));
+
+  // TODO: grab new package ID from the events above. Do not rely on the RPC
+  // call because it may give you a stale package ID after the upgrade.
+
+  // const migrateResults = await migrateWormhole(
+  //   wallet,
+  //   WORMHOLE_STATE_ID,
+  //   signedVaa
+  // );
+  // console.log("tx digest", migrateResults.digest);
+  // console.log("tx effects", JSON.stringify(migrateResults.effects!));
+  // console.log("tx events", JSON.stringify(migrateResults.events!));
+
+  // Clean up.
+  cleanUpPackageDirectory(dstWormholePath);
+}
+
+main();
+
+// Yeah buddy.
+
+function buildForBytecodeAndDigest(packagePath: string) {
+  const buildOutput: {
+    modules: string[];
+    dependencies: string[];
+    digest: number[];
+  } = JSON.parse(
+    execSync(
+      `sui move build --dump-bytecode-as-base64 -p ${packagePath} 2> /dev/null`,
+      { encoding: "utf-8" }
+    )
+  );
+  return {
+    modules: buildOutput.modules.map((m: string) => Array.from(fromB64(m))),
+    dependencies: buildOutput.dependencies.map((d: string) =>
+      normalizeSuiObjectId(d)
+    ),
+    digest: Buffer.from(buildOutput.digest),
+  };
+}
+
+async function getPackageId(
+  provider: JsonRpcProvider,
+  stateId: string
+): Promise<string> {
+  const state = await provider
+    .getObject({
+      id: stateId,
+      options: {
+        showContent: true,
+      },
+    })
+    .then((result) => {
+      if (result.data?.content?.dataType == "moveObject") {
+        return result.data.content.fields;
+      }
+
+      throw new Error("not move object");
+    });
+
+  if ("upgrade_cap" in state) {
+    return state.upgrade_cap.fields.package;
+  }
+
+  throw new Error("upgrade_cap not found");
+}
+
+async function buildAndUpgradeWormhole(
+  signer: RawSigner,
+  wormholeStateId: string,
+  modules: number[][],
+  dependencies: string[],
+  signedVaa: Buffer
+) {
+  const wormholePackage = await getPackageId(signer.provider, wormholeStateId);
+
+  const tx = new TransactionBlock();
+
+  const [verifiedVaa] = tx.moveCall({
+    target: `${wormholePackage}::vaa::parse_and_verify`,
+    arguments: [
+      tx.object(wormholeStateId),
+      tx.pure(Array.from(signedVaa)),
+      tx.object(SUI_CLOCK_OBJECT_ID),
+    ],
+  });
+  const [decreeTicket] = tx.moveCall({
+    target: `${wormholePackage}::upgrade_contract::authorize_governance`,
+    arguments: [tx.object(wormholeStateId)],
+  });
+  const [decreeReceipt] = tx.moveCall({
+    target: `${wormholePackage}::governance_message::verify_vaa`,
+    arguments: [tx.object(wormholeStateId), verifiedVaa, decreeTicket],
+    typeArguments: [`${wormholePackage}::upgrade_contract::GovernanceWitness`],
+  });
+
+  // Authorize upgrade.
+  const [upgradeTicket] = tx.moveCall({
+    target: `${wormholePackage}::upgrade_contract::authorize_upgrade`,
+    arguments: [tx.object(wormholeStateId), decreeReceipt],
+  });
+
+  // Build and generate modules and dependencies for upgrade.
+  const [upgradeReceipt] = tx.upgrade({
+    modules,
+    dependencies,
+    packageId: wormholePackage,
+    ticket: upgradeTicket,
+  });
+
+  // Commit upgrade.
+  tx.moveCall({
+    target: `${wormholePackage}::upgrade_contract::commit_upgrade`,
+    arguments: [tx.object(wormholeStateId), upgradeReceipt],
+  });
+
+  // Cannot auto compute gas budget, so we need to configure it manually.
+  // Gas ~215m.
+  //tx.setGasBudget(1_000_000_000n);
+
+  return signer.signAndExecuteTransactionBlock({
+    transactionBlock: tx,
+    options: {
+      showEffects: true,
+      showEvents: true,
+    },
+  });
+}
+
+async function migrateWormhole(
+  signer: RawSigner,
+  wormholeStateId: string,
+  signedUpgradeVaa: Buffer
+) {
+  const contractPackage = await getPackageId(signer.provider, wormholeStateId);
+
+  const tx = new TransactionBlock();
+  tx.moveCall({
+    target: `${contractPackage}::migrate::migrate`,
+    arguments: [
+      tx.object(wormholeStateId),
+      tx.pure(Array.from(signedUpgradeVaa)),
+      tx.object(SUI_CLOCK_OBJECT_ID),
+    ],
+  });
+
+  return signer.signAndExecuteTransactionBlock({
+    transactionBlock: tx,
+    options: {
+      showEffects: true,
+      showEvents: true,
+    },
+  });
+}
+
+function setUpWormholeDirectory(
+  srcWormholePath: string,
+  dstWormholePath: string
+) {
+  fs.cpSync(srcWormholePath, dstWormholePath, { recursive: true });
+
+  // Remove irrelevant files. This part is not necessary, but is helpful
+  // for debugging a clean package directory.
+  const removeThese = [
+    "Move.devnet.toml",
+    "Move.lock",
+    "Makefile",
+    "README.md",
+    "build",
+  ];
+  for (const basename of removeThese) {
+    fs.rmSync(`${dstWormholePath}/${basename}`, {
+      recursive: true,
+      force: true,
+    });
+  }
+
+  // Fix Move.toml file.
+  const moveTomlPath = `${dstWormholePath}/Move.toml`;
+  const moveToml = fs.readFileSync(moveTomlPath, "utf-8");
+  fs.writeFileSync(
+    moveTomlPath,
+    moveToml.replace(`wormhole = "_"`, `wormhole = "0x0"`),
+    "utf-8"
+  );
+}
+
+function cleanUpPackageDirectory(packagePath: string) {
+  fs.rmSync(packagePath, { recursive: true, force: true });
+}

+ 12 - 0
target_chains/sui/vendor/wormhole_movement_testnet/testing/sui_config/client.yaml

@@ -0,0 +1,12 @@
+---
+keystore:
+  File: sui_config/sui.keystore
+envs:
+  - alias: localnet
+    rpc: "http://0.0.0.0:9000"
+    ws: ~
+  - alias: devnet
+    rpc: "https://fullnode.devnet.sui.io:443"
+    ws: ~
+active_env: localnet
+active_address: "0xed867315e3f7c83ae82e6d5858b6a6cc57c291fd84f7509646ebc8162169cf96"

+ 53 - 0
target_chains/sui/vendor/wormhole_movement_testnet/testing/sui_config/fullnode.yaml

@@ -0,0 +1,53 @@
+---
+protocol-key-pair:
+  value: W+hPTVWhdFgzHs3YuRHV6gLfgFhHA1WG0pisIXiN8E8=
+worker-key-pair:
+  value: AApEvpZE1O+2GMqZ1AbRE3+Kmgr1O5mdsMZ6I/gLpVSy
+account-key-pair:
+  value: AN7ZHgjN8G7Nw7Q8NtY9TisPBjmEYpdUzbczjqR98XLh
+network-key-pair:
+  value: AAnB6/zZooq4xDtB7oM/GeTSCh5tBxKAyJwWOMPlEJ4R
+db-path: sui_config/authorities_db/full_node_db
+network-address: /ip4/127.0.0.1/tcp/36683/http
+json-rpc-address: "0.0.0.0:9000"
+metrics-address: "127.0.0.1:35915"
+admin-interface-port: 44319
+enable-event-processing: true
+enable-index-processing: true
+grpc-load-shed: ~
+grpc-concurrency-limit: ~
+p2p-config:
+  listen-address: "127.0.0.1:38187"
+  external-address: /ip4/127.0.0.1/udp/38187
+  seed-peers:
+    - peer-id: ce60e3077e02a3683436af450f3a4511b4c40b158956637caf9ccf11391e7e10
+      address: /ip4/127.0.0.1/udp/44061
+    - peer-id: 5f0f42cb3fb20dd577703388320964f9351d997313c04a032247060d214b2e71
+      address: /ip4/127.0.0.1/udp/46335
+    - peer-id: 6d9095130b1536c0c9218ea9feb0f36685a6fa0b3b1e67d256cc4fb340a48d69
+      address: /ip4/127.0.0.1/udp/32965
+    - peer-id: b2915bf787845a55c24e18fdc162a575eb02d23bae3f9e566d7c51ebcfeb4a42
+      address: /ip4/127.0.0.1/udp/39889
+genesis:
+  genesis-file-location: sui_config/genesis.blob
+authority-store-pruning-config:
+  num-latest-epoch-dbs-to-retain: 3
+  epoch-db-pruning-period-secs: 3600
+  num-epochs-to-retain: 2
+  max-checkpoints-in-batch: 200
+  max-transactions-in-batch: 1000
+  use-range-deletion: true
+end-of-epoch-broadcast-channel-capacity: 128
+checkpoint-executor-config:
+  checkpoint-execution-max-concurrency: 200
+  local-execution-timeout-sec: 30
+db-checkpoint-config:
+  perform-db-checkpoints-at-epoch-end: false
+indirect-objects-threshold: 18446744073709551615
+expensive-safety-check-config:
+  enable-epoch-sui-conservation-check: false
+  enable-deep-per-tx-sui-conservation-check: false
+  force-disable-epoch-sui-conservation-check: false
+  enable-state-consistency-check: false
+  force-disable-state-consistency-check: false
+  enable-move-vm-paranoid-checks: false

BIN=BIN
target_chains/sui/vendor/wormhole_movement_testnet/testing/sui_config/genesis.blob


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 323 - 0
target_chains/sui/vendor/wormhole_movement_testnet/testing/sui_config/network.yaml


+ 7 - 0
target_chains/sui/vendor/wormhole_movement_testnet/testing/sui_config/sui.keystore

@@ -0,0 +1,7 @@
+[
+  "AB522qKKEsXMTFRD2SG3Het/02S/ZBOugmcH3R1CDG6l",
+  "AOmPq9B16F3W3ijO/4s9hI6v8LdiYCawKAW31PKpg4Qp",
+  "AOLhc0ryVWnD5LmqH3kCHruBpVV+68EWjEGu2eC9gndK",
+  "AKCo1FyhQ0zUpnoZLmGJJ+8LttTrt56W87Ho4vBF+R+8",
+  "AGA20wtGcwbcNAG4nwapbQ5wIuXwkYQEWFUoSVAxctHb"
+]

+ 81 - 0
target_chains/sui/vendor/wormhole_movement_testnet/testing/sui_config/validator-config-0.yaml

@@ -0,0 +1,81 @@
+---
+protocol-key-pair:
+  value: VTDx4HjVmRBqdqBWg2zN+zcFE20io3CrBchGy/iV1lo=
+worker-key-pair:
+  value: ABlC9PMmIQHjxila3AEOXDxwCSuodcvJh2Q5O5HIB00K
+account-key-pair:
+  value: AIV4Ng6OYQf6irjVCZly5X7dSpdFpwoWtdAx9u4PANRl
+network-key-pair:
+  value: AOqJl2rHMnroe26vjkkIuWGBD/y6HzQG6MK5bC9njF0s
+db-path: sui_config/authorities_db/99f25ef61f80
+network-address: /ip4/127.0.0.1/tcp/36459/http
+json-rpc-address: "127.0.0.1:38133"
+metrics-address: "127.0.0.1:44135"
+admin-interface-port: 33917
+consensus-config:
+  address: /ip4/127.0.0.1/tcp/41459/http
+  db-path: sui_config/consensus_db/99f25ef61f80
+  internal-worker-address: ~
+  max-pending-transactions: ~
+  narwhal-config:
+    header_num_of_batches_threshold: 32
+    max_header_num_of_batches: 1000
+    max_header_delay: 2000ms
+    min_header_delay: 500ms
+    gc_depth: 50
+    sync_retry_delay: 5000ms
+    sync_retry_nodes: 3
+    batch_size: 500000
+    max_batch_delay: 100ms
+    block_synchronizer:
+      range_synchronize_timeout: 30000ms
+      certificates_synchronize_timeout: 30000ms
+      payload_synchronize_timeout: 30000ms
+      payload_availability_timeout: 30000ms
+      handler_certificate_deliver_timeout: 30000ms
+    consensus_api_grpc:
+      socket_addr: /ip4/127.0.0.1/tcp/44689/http
+      get_collections_timeout: 5000ms
+      remove_collections_timeout: 5000ms
+    max_concurrent_requests: 500000
+    prometheus_metrics:
+      socket_addr: /ip4/127.0.0.1/tcp/33219/http
+    network_admin_server:
+      primary_network_admin_server_port: 33945
+      worker_network_admin_server_base_port: 38081
+    anemo:
+      send_certificate_rate_limit: ~
+      get_payload_availability_rate_limit: ~
+      get_certificates_rate_limit: ~
+      report_batch_rate_limit: ~
+      request_batch_rate_limit: ~
+enable-event-processing: false
+enable-index-processing: true
+grpc-load-shed: ~
+grpc-concurrency-limit: 20000000000
+p2p-config:
+  listen-address: "127.0.0.1:44061"
+  external-address: /ip4/127.0.0.1/udp/44061
+genesis:
+  genesis-file-location: sui_config/genesis.blob
+authority-store-pruning-config:
+  num-latest-epoch-dbs-to-retain: 3
+  epoch-db-pruning-period-secs: 3600
+  num-epochs-to-retain: 2
+  max-checkpoints-in-batch: 200
+  max-transactions-in-batch: 1000
+  use-range-deletion: true
+end-of-epoch-broadcast-channel-capacity: 128
+checkpoint-executor-config:
+  checkpoint-execution-max-concurrency: 200
+  local-execution-timeout-sec: 30
+db-checkpoint-config:
+  perform-db-checkpoints-at-epoch-end: false
+indirect-objects-threshold: 18446744073709551615
+expensive-safety-check-config:
+  enable-epoch-sui-conservation-check: false
+  enable-deep-per-tx-sui-conservation-check: false
+  force-disable-epoch-sui-conservation-check: false
+  enable-state-consistency-check: false
+  force-disable-state-consistency-check: false
+  enable-move-vm-paranoid-checks: false

+ 81 - 0
target_chains/sui/vendor/wormhole_movement_testnet/testing/sui_config/validator-config-1.yaml

@@ -0,0 +1,81 @@
+---
+protocol-key-pair:
+  value: avYcyVgYMXTyaUYh9IRwLK0gSzl7YF6ZQDAbrS1Bhvo=
+worker-key-pair:
+  value: AGsxCVxeIZ6fscvGECzV93Hi4JkqM4zMYEA8wBGfXQrz
+account-key-pair:
+  value: AF9cOMxTRAUTOws2M8W5slHf41HITA+M3nqXHT6nlH6S
+network-key-pair:
+  value: ALH/8qz2YlwAuxY/hOvuXiglYq0e4LLU1/lyf5uKgPY8
+db-path: sui_config/authorities_db/8dcff6d15504
+network-address: /ip4/127.0.0.1/tcp/33355/http
+json-rpc-address: "127.0.0.1:39573"
+metrics-address: "127.0.0.1:45851"
+admin-interface-port: 35739
+consensus-config:
+  address: /ip4/127.0.0.1/tcp/42959/http
+  db-path: sui_config/consensus_db/8dcff6d15504
+  internal-worker-address: ~
+  max-pending-transactions: ~
+  narwhal-config:
+    header_num_of_batches_threshold: 32
+    max_header_num_of_batches: 1000
+    max_header_delay: 2000ms
+    min_header_delay: 500ms
+    gc_depth: 50
+    sync_retry_delay: 5000ms
+    sync_retry_nodes: 3
+    batch_size: 500000
+    max_batch_delay: 100ms
+    block_synchronizer:
+      range_synchronize_timeout: 30000ms
+      certificates_synchronize_timeout: 30000ms
+      payload_synchronize_timeout: 30000ms
+      payload_availability_timeout: 30000ms
+      handler_certificate_deliver_timeout: 30000ms
+    consensus_api_grpc:
+      socket_addr: /ip4/127.0.0.1/tcp/37001/http
+      get_collections_timeout: 5000ms
+      remove_collections_timeout: 5000ms
+    max_concurrent_requests: 500000
+    prometheus_metrics:
+      socket_addr: /ip4/127.0.0.1/tcp/39831/http
+    network_admin_server:
+      primary_network_admin_server_port: 39853
+      worker_network_admin_server_base_port: 36429
+    anemo:
+      send_certificate_rate_limit: ~
+      get_payload_availability_rate_limit: ~
+      get_certificates_rate_limit: ~
+      report_batch_rate_limit: ~
+      request_batch_rate_limit: ~
+enable-event-processing: false
+enable-index-processing: true
+grpc-load-shed: ~
+grpc-concurrency-limit: 20000000000
+p2p-config:
+  listen-address: "127.0.0.1:46335"
+  external-address: /ip4/127.0.0.1/udp/46335
+genesis:
+  genesis-file-location: sui_config/genesis.blob
+authority-store-pruning-config:
+  num-latest-epoch-dbs-to-retain: 3
+  epoch-db-pruning-period-secs: 3600
+  num-epochs-to-retain: 2
+  max-checkpoints-in-batch: 200
+  max-transactions-in-batch: 1000
+  use-range-deletion: true
+end-of-epoch-broadcast-channel-capacity: 128
+checkpoint-executor-config:
+  checkpoint-execution-max-concurrency: 200
+  local-execution-timeout-sec: 30
+db-checkpoint-config:
+  perform-db-checkpoints-at-epoch-end: false
+indirect-objects-threshold: 18446744073709551615
+expensive-safety-check-config:
+  enable-epoch-sui-conservation-check: false
+  enable-deep-per-tx-sui-conservation-check: false
+  force-disable-epoch-sui-conservation-check: false
+  enable-state-consistency-check: false
+  force-disable-state-consistency-check: false
+  enable-move-vm-paranoid-checks: false

+ 81 - 0
target_chains/sui/vendor/wormhole_movement_testnet/testing/sui_config/validator-config-2.yaml

@@ -0,0 +1,81 @@
+---
+protocol-key-pair:
+  value: OXnx3yM1C/ppgnDMx/o1d49fJs7E05kq11mXNae/O+I=
+worker-key-pair:
+  value: AHXs8DP7EccyxtxAGq/m33LgvOApXs4JStH3PLAe9vGw
+account-key-pair:
+  value: AC8vF9E3QYf0aTyBZWlSzJJXETvV5vYkOtEJl+DWQMlk
+network-key-pair:
+  value: AOapcKU6mW5SopFM6eBSiXgbuPJTz11CiEqM+SJGIEOF
+db-path: sui_config/authorities_db/addeef94d898
+network-address: /ip4/127.0.0.1/tcp/34633/http
+json-rpc-address: "127.0.0.1:38025"
+metrics-address: "127.0.0.1:43451"
+admin-interface-port: 36793
+consensus-config:
+  address: /ip4/127.0.0.1/tcp/40307/http
+  db-path: sui_config/consensus_db/addeef94d898
+  internal-worker-address: ~
+  max-pending-transactions: ~
+  narwhal-config:
+    header_num_of_batches_threshold: 32
+    max_header_num_of_batches: 1000
+    max_header_delay: 2000ms
+    min_header_delay: 500ms
+    gc_depth: 50
+    sync_retry_delay: 5000ms
+    sync_retry_nodes: 3
+    batch_size: 500000
+    max_batch_delay: 100ms
+    block_synchronizer:
+      range_synchronize_timeout: 30000ms
+      certificates_synchronize_timeout: 30000ms
+      payload_synchronize_timeout: 30000ms
+      payload_availability_timeout: 30000ms
+      handler_certificate_deliver_timeout: 30000ms
+    consensus_api_grpc:
+      socket_addr: /ip4/127.0.0.1/tcp/37445/http
+      get_collections_timeout: 5000ms
+      remove_collections_timeout: 5000ms
+    max_concurrent_requests: 500000
+    prometheus_metrics:
+      socket_addr: /ip4/127.0.0.1/tcp/43943/http
+    network_admin_server:
+      primary_network_admin_server_port: 39611
+      worker_network_admin_server_base_port: 38377
+    anemo:
+      send_certificate_rate_limit: ~
+      get_payload_availability_rate_limit: ~
+      get_certificates_rate_limit: ~
+      report_batch_rate_limit: ~
+      request_batch_rate_limit: ~
+enable-event-processing: false
+enable-index-processing: true
+grpc-load-shed: ~
+grpc-concurrency-limit: 20000000000
+p2p-config:
+  listen-address: "127.0.0.1:32965"
+  external-address: /ip4/127.0.0.1/udp/32965
+genesis:
+  genesis-file-location: sui_config/genesis.blob
+authority-store-pruning-config:
+  num-latest-epoch-dbs-to-retain: 3
+  epoch-db-pruning-period-secs: 3600
+  num-epochs-to-retain: 2
+  max-checkpoints-in-batch: 200
+  max-transactions-in-batch: 1000
+  use-range-deletion: true
+end-of-epoch-broadcast-channel-capacity: 128
+checkpoint-executor-config:
+  checkpoint-execution-max-concurrency: 200
+  local-execution-timeout-sec: 30
+db-checkpoint-config:
+  perform-db-checkpoints-at-epoch-end: false
+indirect-objects-threshold: 18446744073709551615
+expensive-safety-check-config:
+  enable-epoch-sui-conservation-check: false
+  enable-deep-per-tx-sui-conservation-check: false
+  force-disable-epoch-sui-conservation-check: false
+  enable-state-consistency-check: false
+  force-disable-state-consistency-check: false
+  enable-move-vm-paranoid-checks: false

+ 81 - 0
target_chains/sui/vendor/wormhole_movement_testnet/testing/sui_config/validator-config-3.yaml

@@ -0,0 +1,81 @@
+---
+protocol-key-pair:
+  value: CyNkjqNVr3HrHTH7f/NLs7u5lUHJzuPAw0PqMTD2y2s=
+worker-key-pair:
+  value: AHd6qvbBv7bTCGGoD1TUR5dOGnwOnYvhHV9ryCUp7rmZ
+account-key-pair:
+  value: ALSCvWwsVryGIwq+n4f9bIPCRqsooGodE/vDaVCSLfjE
+network-key-pair:
+  value: APFCK1pRVxn9PDt+KzWx52+EY5nzaZZU2GF9RZoQY58Y
+db-path: sui_config/authorities_db/b3fd5efb5c87
+network-address: /ip4/127.0.0.1/tcp/33953/http
+json-rpc-address: "127.0.0.1:35625"
+metrics-address: "127.0.0.1:37813"
+admin-interface-port: 46405
+consensus-config:
+  address: /ip4/127.0.0.1/tcp/43213/http
+  db-path: sui_config/consensus_db/b3fd5efb5c87
+  internal-worker-address: ~
+  max-pending-transactions: ~
+  narwhal-config:
+    header_num_of_batches_threshold: 32
+    max_header_num_of_batches: 1000
+    max_header_delay: 2000ms
+    min_header_delay: 500ms
+    gc_depth: 50
+    sync_retry_delay: 5000ms
+    sync_retry_nodes: 3
+    batch_size: 500000
+    max_batch_delay: 100ms
+    block_synchronizer:
+      range_synchronize_timeout: 30000ms
+      certificates_synchronize_timeout: 30000ms
+      payload_synchronize_timeout: 30000ms
+      payload_availability_timeout: 30000ms
+      handler_certificate_deliver_timeout: 30000ms
+    consensus_api_grpc:
+      socket_addr: /ip4/127.0.0.1/tcp/46745/http
+      get_collections_timeout: 5000ms
+      remove_collections_timeout: 5000ms
+    max_concurrent_requests: 500000
+    prometheus_metrics:
+      socket_addr: /ip4/127.0.0.1/tcp/38817/http
+    network_admin_server:
+      primary_network_admin_server_port: 34929
+      worker_network_admin_server_base_port: 37447
+    anemo:
+      send_certificate_rate_limit: ~
+      get_payload_availability_rate_limit: ~
+      get_certificates_rate_limit: ~
+      report_batch_rate_limit: ~
+      request_batch_rate_limit: ~
+enable-event-processing: false
+enable-index-processing: true
+grpc-load-shed: ~
+grpc-concurrency-limit: 20000000000
+p2p-config:
+  listen-address: "127.0.0.1:39889"
+  external-address: /ip4/127.0.0.1/udp/39889
+genesis:
+  genesis-file-location: sui_config/genesis.blob
+authority-store-pruning-config:
+  num-latest-epoch-dbs-to-retain: 3
+  epoch-db-pruning-period-secs: 3600
+  num-epochs-to-retain: 2
+  max-checkpoints-in-batch: 200
+  max-transactions-in-batch: 1000
+  use-range-deletion: true
+end-of-epoch-broadcast-channel-capacity: 128
+checkpoint-executor-config:
+  checkpoint-execution-max-concurrency: 200
+  local-execution-timeout-sec: 30
+db-checkpoint-config:
+  perform-db-checkpoints-at-epoch-end: false
+indirect-objects-threshold: 18446744073709551615
+expensive-safety-check-config:
+  enable-epoch-sui-conservation-check: false
+  enable-deep-per-tx-sui-conservation-check: false
+  force-disable-epoch-sui-conservation-check: false
+  enable-state-consistency-check: false
+  force-disable-state-consistency-check: false
+  enable-move-vm-paranoid-checks: false

+ 12 - 0
target_chains/sui/vendor/wormhole_movement_testnet/testing/tsconfig.json

@@ -0,0 +1,12 @@
+{
+    "compilerOptions": {
+      "types": ["mocha", "chai"],
+      "typeRoots": ["./node_modules/@types"],
+      "lib": ["es2020"],
+      "module": "commonjs",
+      "target": "es2020",
+      "strict": true,
+      "resolveJsonModule": true,
+      "esModuleInterop": true
+    }
+  }

+ 1 - 0
target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/.gitignore

@@ -0,0 +1 @@
+build

+ 18 - 0
target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/Makefile

@@ -0,0 +1,18 @@
+-include ../../Makefile.help
+
+VERSION = $(shell grep -Po "version = \"\K[^\"]*" Move.toml | sed "s/\./_/g")
+
+.PHONY: clean
+clean:
+	rm -rf build
+
+.PHONY: check
+## Build contract
+check:
+	sui move build -d
+
+.PHONY: test
+## Run tests
+test: check
+	grep "public(friend) fun current_version(): V__${VERSION} {" sources/version_control.move
+	sui move test -t 1

+ 14 - 0
target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/Move.devnet.toml

@@ -0,0 +1,14 @@
+[package]
+name = "TokenBridge"
+version = "0.2.0"
+
+[dependencies.Sui]
+git = "https://github.com/MystenLabs/sui.git"
+subdir = "crates/sui-framework/packages/sui-framework"
+rev = "041c5f2bae2fe52079e44b70514333532d69f4e6"
+
+[dependencies.Wormhole]
+local = "../wormhole"
+
+[addresses]
+token_bridge = "_"

+ 39 - 0
target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/Move.lock

@@ -0,0 +1,39 @@
+# @generated by Move, please check-in and do not edit manually.
+
+[move]
+version = 0
+manifest_digest = "868DAEC26B76907DDD55CB64F8F3373546E028B90763B6BD8D1544F7EB721AAD"
+deps_digest = "060AD7E57DFB13104F21BE5F5C3759D03F0553FC3229247D9A7A6B45F50D03A3"
+
+dependencies = [
+  { name = "Sui" },
+]
+
+dev-dependencies = [
+  { name = "Wormhole" },
+]
+
+[[move.package]]
+name = "MoveStdlib"
+source = { git = "https://github.com/MystenLabs/sui.git", rev = "041c5f2bae2fe52079e44b70514333532d69f4e6", subdir = "crates/sui-framework/packages/move-stdlib" }
+
+[[move.package]]
+name = "Sui"
+source = { git = "https://github.com/MystenLabs/sui.git", rev = "041c5f2bae2fe52079e44b70514333532d69f4e6", subdir = "crates/sui-framework/packages/sui-framework" }
+
+dependencies = [
+  { name = "MoveStdlib" },
+]
+
+[[move.package]]
+name = "Wormhole"
+source = { local = "../wormhole" }
+
+dependencies = [
+  { name = "Sui" },
+]
+
+[move.toolchain-version]
+compiler-version = "1.19.0"
+edition = "legacy"
+flavor = "sui"

+ 15 - 0
target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/Move.mainnet.toml

@@ -0,0 +1,15 @@
+[package]
+name = "TokenBridge"
+version = "0.2.0"
+published-at = "0x26efee2b51c911237888e5dc6702868abca3c7ac12c53f76ef8eba0697695e3d"
+
+[dependencies.Sui]
+git = "https://github.com/MystenLabs/sui.git"
+subdir = "crates/sui-framework/packages/sui-framework"
+rev = "041c5f2bae2fe52079e44b70514333532d69f4e6"
+
+[dependencies.Wormhole]
+local = "../wormhole"
+
+[addresses]
+token_bridge = "0x26efee2b51c911237888e5dc6702868abca3c7ac12c53f76ef8eba0697695e3d"

+ 15 - 0
target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/Move.testnet.toml

@@ -0,0 +1,15 @@
+[package]
+name = "TokenBridge"
+version = "0.2.0"
+published-at = "0x562760fc51d90d4ae1835bac3e91e0e6987d3497b06f066941d3e51f6e8d76d0"
+
+[dependencies.Sui]
+git = "https://github.com/MystenLabs/sui.git"
+subdir = "crates/sui-framework/packages/sui-framework"
+rev = "041c5f2bae2fe52079e44b70514333532d69f4e6"
+
+[dependencies.Wormhole]
+local = "../wormhole"
+
+[addresses]
+token_bridge = "0x562760fc51d90d4ae1835bac3e91e0e6987d3497b06f066941d3e51f6e8d76d0"

+ 21 - 0
target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/Move.toml

@@ -0,0 +1,21 @@
+[package]
+name = "TokenBridge"
+version = "0.2.0"
+
+[dependencies.Sui]
+git = "https://github.com/MystenLabs/sui.git"
+subdir = "crates/sui-framework/packages/sui-framework"
+rev = "041c5f2bae2fe52079e44b70514333532d69f4e6"
+
+[dependencies.Wormhole]
+local = "../wormhole"
+
+[addresses]
+token_bridge = "_"
+
+[dev-dependencies.Wormhole]
+local = "../wormhole"
+
+[dev-addresses]
+wormhole = "0x100"
+token_bridge = "0x200"

+ 385 - 0
target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/attest_token.move

@@ -0,0 +1,385 @@
+// SPDX-License-Identifier: Apache 2
+
+/// This module implements the method `attest_token` which allows someone
+/// to send asset metadata of a coin type native to Sui. Part of this process
+/// is registering this asset in the `TokenRegistry`.
+///
+/// NOTE: If an asset has not been attested for, it cannot be bridged using
+/// `transfer_tokens` or `transfer_tokens_with_payload`.
+///
+/// See `asset_meta` module for serialization and deserialization of Wormhole
+/// message payload.
+module token_bridge::attest_token {
+    use sui::coin::{CoinMetadata};
+    use wormhole::publish_message::{MessageTicket};
+
+    use token_bridge::asset_meta::{Self};
+    use token_bridge::create_wrapped::{Self};
+    use token_bridge::state::{Self, State, LatestOnly};
+    use token_bridge::token_registry::{Self};
+
+    /// Coin type belongs to a wrapped asset.
+    const E_WRAPPED_ASSET: u64 = 0;
+    /// Coin type belongs to an untrusted contract from `create_wrapped` which
+    /// has not completed registration.
+    const E_FROM_CREATE_WRAPPED: u64 = 1;
+
+    /// `attest_token` takes `CoinMetadata` of a coin type and generates a
+    /// `MessageTicket` with encoded asset metadata for a foreign Token Bridge
+    /// contract to consume and create a wrapped asset reflecting this Sui
+    /// asset. Asset metadata is encoded using `AssetMeta`.
+    ///
+    /// See `token_registry` and `asset_meta` module for more info.
+    public fun attest_token<CoinType>(
+        token_bridge_state: &mut State,
+        coin_meta: &CoinMetadata<CoinType>,
+        nonce: u32
+    ): MessageTicket {
+        // This capability ensures that the current build version is used.
+        let latest_only = state::assert_latest_only(token_bridge_state);
+
+        // Encode Wormhole message payload.
+        let encoded_asset_meta =
+            serialize_asset_meta(&latest_only, token_bridge_state, coin_meta);
+
+        // Prepare Wormhole message.
+        state::prepare_wormhole_message(
+            &latest_only,
+            token_bridge_state,
+            nonce,
+            encoded_asset_meta
+        )
+    }
+
+    fun serialize_asset_meta<CoinType>(
+        latest_only: &LatestOnly,
+        token_bridge_state: &mut State,
+        coin_meta: &CoinMetadata<CoinType>,
+    ): vector<u8> {
+        let registry = state::borrow_token_registry(token_bridge_state);
+
+        // Register if it is a new asset.
+        //
+        // NOTE: We don't want to abort if the asset is already registered
+        // because we may want to send asset metadata again after registration
+        // (the owner of a particular `CoinType` can change `CoinMetadata` any
+        // time after we register the asset).
+        if (token_registry::has<CoinType>(registry)) {
+            let asset_info = token_registry::verified_asset<CoinType>(registry);
+            // If this asset is already registered, there should already
+            // be canonical info associated with this coin type.
+            assert!(
+                !token_registry::is_wrapped(&asset_info),
+                E_WRAPPED_ASSET
+            );
+        } else {
+            // Before we consider registering, we should not accidentally
+            // perform this registration that may be the `CoinMetadata` from
+            // `create_wrapped::prepare_registration`, which has empty fields.
+            assert!(
+                !create_wrapped::incomplete_metadata(coin_meta),
+                E_FROM_CREATE_WRAPPED
+            );
+
+            // Now register it.
+            token_registry::add_new_native(
+                state::borrow_mut_token_registry(
+                    latest_only,
+                    token_bridge_state
+                ),
+                coin_meta
+            );
+        };
+
+        asset_meta::serialize(asset_meta::from_metadata(coin_meta))
+    }
+
+    #[test_only]
+    public fun serialize_asset_meta_test_only<CoinType>(
+        token_bridge_state: &mut State,
+        coin_metadata: &CoinMetadata<CoinType>,
+    ): vector<u8> {
+        // This capability ensures that the current build version is used.
+        let latest_only = state::assert_latest_only(token_bridge_state);
+
+        serialize_asset_meta(&latest_only, token_bridge_state, coin_metadata)
+    }
+}
+
+#[test_only]
+module token_bridge::attest_token_tests {
+    use std::ascii::{Self};
+    use std::string::{Self};
+    use sui::coin::{Self};
+    use sui::test_scenario::{Self};
+    use wormhole::publish_message::{Self};
+    use wormhole::state::{chain_id};
+
+    use token_bridge::asset_meta::{Self};
+    use token_bridge::attest_token::{Self};
+    use token_bridge::coin_native_10::{Self, COIN_NATIVE_10};
+    use token_bridge::coin_wrapped_7::{Self, COIN_WRAPPED_7};
+    use token_bridge::native_asset::{Self};
+    use token_bridge::state::{Self};
+    use token_bridge::token_bridge_scenario::{
+        person,
+        return_state,
+        set_up_wormhole_and_token_bridge,
+        take_state,
+    };
+    use token_bridge::token_registry::{Self};
+
+    #[test]
+    fun test_attest_token() {
+        use token_bridge::attest_token::{attest_token};
+
+        let user = person();
+        let my_scenario = test_scenario::begin(user);
+        let scenario = &mut my_scenario;
+
+        // Publish coin.
+        coin_native_10::init_test_only(test_scenario::ctx(scenario));
+
+        // Set up contracts.
+        let wormhole_fee = 350;
+        set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, user);
+
+        let token_bridge_state = take_state(scenario);
+        let coin_meta = coin_native_10::take_metadata(scenario);
+
+        // Emit `AssetMeta` payload.
+        let prepared_msg =
+            attest_token(
+                &mut token_bridge_state,
+                &coin_meta,
+                1234, // nonce
+            );
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, user);
+
+        // Check that asset is registered.
+        {
+            let registry =
+                state::borrow_token_registry(&token_bridge_state);
+            let verified =
+                token_registry::verified_asset<COIN_NATIVE_10>(registry);
+            assert!(!token_registry::is_wrapped(&verified), 0);
+
+            let asset = token_registry::borrow_native<COIN_NATIVE_10>(registry);
+
+            let expected_token_address =
+                native_asset::canonical_address(&coin_meta);
+            assert!(
+                native_asset::token_address(asset) == expected_token_address,
+                0
+            );
+            assert!(native_asset::decimals(asset) == 10, 0);
+
+            let (
+                token_chain,
+                token_address
+            ) = native_asset::canonical_info(asset);
+            assert!(token_chain == chain_id(), 0);
+            assert!(token_address == expected_token_address, 0);
+
+            assert!(native_asset::custody(asset) == 0, 0);
+        };
+
+        // Clean up for next call.
+        publish_message::destroy(prepared_msg);
+
+        // Update metadata.
+        let new_symbol = {
+            use std::vector::{Self};
+
+            let symbol = coin::get_symbol(&coin_meta);
+            let buf = ascii::into_bytes(symbol);
+            vector::reverse(&mut buf);
+
+            ascii::string(buf)
+        };
+
+        let new_name = coin::get_name(&coin_meta);
+        string::append(&mut new_name, string::utf8(b"??? and profit"));
+
+        let treasury_cap = coin_native_10::take_treasury_cap(scenario);
+        coin::update_symbol(&treasury_cap, &mut coin_meta, new_symbol);
+        coin::update_name(&treasury_cap, &mut coin_meta, new_name);
+
+        // We should be able to call `attest_token` any time after.
+        let prepared_msg =
+            attest_token(
+                &mut token_bridge_state,
+                &coin_meta,
+                1234, // nonce
+            );
+
+        // Clean up.
+        publish_message::destroy(prepared_msg);
+        return_state(token_bridge_state);
+        coin_native_10::return_globals(treasury_cap, coin_meta);
+
+        // Done.
+        test_scenario::end(my_scenario);
+    }
+
+    #[test]
+    fun test_serialize_asset_meta() {
+        use token_bridge::attest_token::{serialize_asset_meta_test_only};
+
+        let user = person();
+        let my_scenario = test_scenario::begin(user);
+        let scenario = &mut my_scenario;
+
+        // Publish coin.
+        coin_native_10::init_test_only(test_scenario::ctx(scenario));
+
+        // Set up contracts.
+        let wormhole_fee = 350;
+        set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
+
+        // Proceed to next operation.
+        test_scenario::next_tx(scenario, user);
+
+        let token_bridge_state = take_state(scenario);
+        let coin_meta = coin_native_10::take_metadata(scenario);
+
+        // Emit `AssetMeta` payload.
+        let serialized =
+            serialize_asset_meta_test_only(&mut token_bridge_state, &coin_meta);
+        let expected_serialized =
+            asset_meta::serialize_test_only(
+                asset_meta::from_metadata_test_only(&coin_meta)
+            );
+        assert!(serialized == expected_serialized, 0);
+
+        // Update metadata.
+        let new_symbol = {
+            use std::vector::{Self};
+
+            let symbol = coin::get_symbol(&coin_meta);
+            let buf = ascii::into_bytes(symbol);
+            vector::reverse(&mut buf);
+
+            ascii::string(buf)
+        };
+
+        let new_name = coin::get_name(&coin_meta);
+        string::append(&mut new_name, string::utf8(b"??? and profit"));
+
+        let treasury_cap = coin_native_10::take_treasury_cap(scenario);
+        coin::update_symbol(&treasury_cap, &mut coin_meta, new_symbol);
+        coin::update_name(&treasury_cap, &mut coin_meta, new_name);
+
+        // Check that the new serialization reflects updated metadata.
+        let expected_serialized =
+            asset_meta::serialize_test_only(
+                asset_meta::from_metadata_test_only(&coin_meta)
+            );
+        assert!(serialized != expected_serialized, 0);
+        let updated_serialized =
+            serialize_asset_meta_test_only(&mut token_bridge_state, &coin_meta);
+        assert!(updated_serialized == expected_serialized, 0);
+
+        // Clean up.
+        return_state(token_bridge_state);
+        coin_native_10::return_globals(treasury_cap, coin_meta);
+
+        // Done.
+        test_scenario::end(my_scenario);
+    }
+
+    #[test]
+    #[expected_failure(abort_code = attest_token::E_FROM_CREATE_WRAPPED)]
+    fun test_cannot_attest_token_from_create_wrapped() {
+        use token_bridge::attest_token::{attest_token};
+
+        let user = person();
+        let my_scenario = test_scenario::begin(user);
+        let scenario = &mut my_scenario;
+
+        // Publish coin.
+        coin_wrapped_7::init_test_only(test_scenario::ctx(scenario));
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, user);
+
+        // Set up contracts.
+        let wormhole_fee = 350;
+        set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, user);
+
+        let token_bridge_state = take_state(scenario);
+        let coin_meta = test_scenario::take_shared(scenario);
+
+        // You shall not pass!
+        let prepared_msg =
+            attest_token<COIN_WRAPPED_7>(
+                &mut token_bridge_state,
+                &coin_meta,
+                1234 // nonce
+            );
+
+        // Clean up.
+        publish_message::destroy(prepared_msg);
+
+        abort 42
+    }
+
+    #[test]
+    #[expected_failure(abort_code = wormhole::package_utils::E_NOT_CURRENT_VERSION)]
+    fun test_cannot_attest_token_outdated_version() {
+        use token_bridge::attest_token::{attest_token};
+
+        let user = person();
+        let my_scenario = test_scenario::begin(user);
+        let scenario = &mut my_scenario;
+
+        // Publish coin.
+        coin_wrapped_7::init_test_only(test_scenario::ctx(scenario));
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, user);
+
+        // Set up contracts.
+        let wormhole_fee = 350;
+        set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, user);
+
+        let token_bridge_state = take_state(scenario);
+        let coin_meta = test_scenario::take_shared(scenario);
+
+        // Conveniently roll version back.
+        state::reverse_migrate_version(&mut token_bridge_state);
+
+        // Simulate executing with an outdated build by upticking the minimum
+        // required version for `publish_message` to something greater than
+        // this build.
+        state::migrate_version_test_only(
+            &mut token_bridge_state,
+            token_bridge::version_control::previous_version_test_only(),
+            token_bridge::version_control::next_version()
+        );
+
+        // You shall not pass!
+        let prepared_msg =
+            attest_token<COIN_WRAPPED_7>(
+                &mut token_bridge_state,
+                &coin_meta,
+                1234 // nonce
+            );
+
+        // Clean up.
+        publish_message::destroy(prepared_msg);
+
+        abort 42
+    }
+}

+ 1228 - 0
target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/complete_transfer.move

@@ -0,0 +1,1228 @@
+// SPDX-License-Identifier: Apache 2
+
+/// This module implements two methods: `authorize_transfer` and
+/// `redeem_relayer_payout`, which are to be executed in a transaction block in
+/// this order.
+///
+/// `authorize_transfer` allows a contract to complete a Token Bridge transfer,
+/// sending assets to the encoded recipient. The coin payout incentive in
+/// redeeming the transfer is packaged in a `RelayerReceipt`.
+///
+/// `redeem_relayer_payout` unpacks the `RelayerReceipt` to release the coin
+/// containing the relayer fee amount.
+///
+/// The purpose of splitting this transfer redemption into two steps is in case
+/// Token Bridge needs to be upgraded and there is a breaking change for this
+/// module, an integrator would not be left broken. It is discouraged to put
+/// `authorize_transfer` in an integrator's package logic. Otherwise, this
+/// integrator needs to be prepared to upgrade his contract to handle the latest
+/// version of `complete_transfer`.
+///
+/// Instead, an integrator is encouraged to execute a transaction block, which
+/// executes `authorize_transfer` using the latest Token Bridge package ID and
+/// to implement `redeem_relayer_payout` in his contract to consume this receipt.
+/// This is similar to how an integrator with Wormhole is not meant to use
+/// `vaa::parse_and_verify` in his contract in case the `vaa` module needs to
+/// be upgraded due to a breaking change.
+///
+/// See `transfer` module for serialization and deserialization of Wormhole
+/// message payload.
+module token_bridge::complete_transfer {
+    use sui::balance::{Self, Balance};
+    use sui::coin::{Self, Coin};
+    use sui::tx_context::{Self, TxContext};
+    use wormhole::external_address::{Self, ExternalAddress};
+
+    use token_bridge::native_asset::{Self};
+    use token_bridge::normalized_amount::{Self, NormalizedAmount};
+    use token_bridge::state::{Self, State, LatestOnly};
+    use token_bridge::token_registry::{Self, VerifiedAsset};
+    use token_bridge::transfer::{Self};
+    use token_bridge::vaa::{Self, TokenBridgeMessage};
+    use token_bridge::wrapped_asset::{Self};
+
+    // Requires `handle_complete_transfer`.
+    friend token_bridge::complete_transfer_with_payload;
+
+    /// Transfer not intended to be received on Sui.
+    const E_TARGET_NOT_SUI: u64 = 0;
+    /// Input token info does not match registered info.
+    const E_CANONICAL_TOKEN_INFO_MISMATCH: u64 = 1;
+
+    /// Event reflecting when a transfer via `complete_transfer` or
+    /// `complete_transfer_with_payload` is successfully executed.
+    struct TransferRedeemed has drop, copy {
+        emitter_chain: u16,
+        emitter_address: ExternalAddress,
+        sequence: u64
+    }
+
+    #[allow(lint(coin_field))]
+    /// This type is only generated from `authorize_transfer` and can only be
+    /// redeemed using `redeem_relayer_payout`. Integrators running relayer
+    /// contracts are expected to implement `redeem_relayer_payout` within their
+    /// contracts and call `authorize_transfer` in a transaction block preceding
+    /// the method that consumes this receipt.
+    struct RelayerReceipt<phantom CoinType> {
+        /// Coin of relayer fee payout.
+        payout: Coin<CoinType>
+    }
+
+    /// `authorize_transfer` deserializes a token transfer VAA payload. Once the
+    /// transfer is authorized, an event (`TransferRedeemed`) is emitted to
+    /// reflect which Token Bridge this transfer originated from. The
+    /// `RelayerReceipt` returned wraps a `Coin` object containing a payout that
+    /// incentivizes someone to execute a transaction on behalf of the encoded
+    /// recipient.
+    ///
+    /// NOTE: This method is guarded by a minimum build version check. This
+    /// method could break backward compatibility on an upgrade.
+    ///
+    /// It is important for integrators to refrain from calling this method
+    /// within their contracts. This method is meant to be called in a
+    /// transaction block, passing the `RelayerReceipt` to a method which calls
+    /// `redeem_relayer_payout` within a contract. If in a circumstance where
+    /// this module has a breaking change in an upgrade, `redeem_relayer_payout`
+    /// will not be affected by this change.
+    ///
+    /// See `redeem_relayer_payout` for more details.
+    public fun authorize_transfer<CoinType>(
+        token_bridge_state: &mut State,
+        msg: TokenBridgeMessage,
+        ctx: &mut TxContext
+    ): RelayerReceipt<CoinType> {
+        // This capability ensures that the current build version is used.
+        let latest_only = state::assert_latest_only(token_bridge_state);
+
+        // Emitting the transfer being redeemed (and disregard return value).
+        emit_transfer_redeemed(&msg);
+
+        // Deserialize transfer message and process.
+        handle_complete_transfer<CoinType>(
+            &latest_only,
+            token_bridge_state,
+            vaa::take_payload(msg),
+            ctx
+        )
+    }
+
+    /// After a transfer is authorized, a relayer contract may unpack the
+    /// `RelayerReceipt` using this method. Coin representing the relaying
+    /// incentive from this receipt is returned. This method is meant to be
+    /// simple. It allows for a coordination with calling `authorize_upgrade`
+    /// before a method that implements `redeem_relayer_payout` in a transaction
+    /// block to consume this receipt.
+    ///
+    /// NOTE: Integrators of Token Bridge collecting relayer fee payouts from
+    /// these token transfers should be calling only this method from their
+    /// contracts. This method is not  guarded by version control (thus not
+    /// requiring a reference to the Token Bridge `State` object), so it is
+    /// intended to work for any package version.
+    public fun redeem_relayer_payout<CoinType>(
+        receipt: RelayerReceipt<CoinType>
+    ): Coin<CoinType> {
+        let RelayerReceipt { payout } = receipt;
+
+        payout
+    }
+
+    /// This is a privileged method only used by `complete_transfer` and
+    /// `complete_transfer_with_payload` modules. This method validates the
+    /// encoded token info with the passed in coin type via the `TokenRegistry`.
+    /// The transfer amount is denormalized and either mints balance of
+    /// wrapped asset or withdraws balance from native asset custody.
+    ///
+    /// Depending on whether this coin is a Token Bridge wrapped asset or a
+    /// natively existing asset on Sui, the coin is either minted or withdrawn
+    /// from Token Bridge's custody.
+    public(friend) fun verify_and_bridge_out<CoinType>(
+        latest_only: &LatestOnly,
+        token_bridge_state: &mut State,
+        token_chain: u16,
+        token_address: ExternalAddress,
+        target_chain: u16,
+        amount: NormalizedAmount
+    ): (
+        VerifiedAsset<CoinType>,
+        Balance<CoinType>
+    ) {
+        // Verify that the intended chain ID for this transfer is for Sui.
+        assert!(
+            target_chain == wormhole::state::chain_id(),
+            E_TARGET_NOT_SUI
+        );
+
+        let asset_info = state::verified_asset<CoinType>(token_bridge_state);
+        assert!(
+            (
+                token_chain == token_registry::token_chain(&asset_info) &&
+                token_address == token_registry::token_address(&asset_info)
+            ),
+            E_CANONICAL_TOKEN_INFO_MISMATCH
+        );
+
+        // De-normalize amount in preparation to take `Balance`.
+        let raw_amount =
+            normalized_amount::to_raw(
+                amount,
+                token_registry::coin_decimals(&asset_info)
+            );
+
+        // If the token is wrapped by Token Bridge, we will mint these tokens.
+        // Otherwise, we will withdraw from custody.
+        let bridged_out = {
+            let registry =
+                state::borrow_mut_token_registry(
+                    latest_only,
+                    token_bridge_state
+                );
+            if (token_registry::is_wrapped(&asset_info)) {
+                wrapped_asset::mint(
+                    token_registry::borrow_mut_wrapped(registry),
+                    raw_amount
+                )
+            } else {
+                native_asset::withdraw(
+                    token_registry::borrow_mut_native(registry),
+                    raw_amount
+                )
+            }
+        };
+
+        (asset_info, bridged_out)
+    }
+
+    /// This method emits source information of the token transfer. Off-chain
+    /// processes may want to observe when transfers have been redeemed.
+    public(friend) fun emit_transfer_redeemed(msg: &TokenBridgeMessage): u16 {
+        let emitter_chain = vaa::emitter_chain(msg);
+
+        // Emit Sui event with `TransferRedeemed`.
+        sui::event::emit(
+            TransferRedeemed {
+                emitter_chain,
+                emitter_address: vaa::emitter_address(msg),
+                sequence: vaa::sequence(msg)
+            }
+        );
+
+        emitter_chain
+    }
+
+    fun handle_complete_transfer<CoinType>(
+        latest_only: &LatestOnly,
+        token_bridge_state: &mut State,
+        transfer_vaa_payload: vector<u8>,
+        ctx: &mut TxContext
+    ): RelayerReceipt<CoinType> {
+        let (
+            amount,
+            token_address,
+            token_chain,
+            recipient,
+            recipient_chain,
+            relayer_fee
+        ) = transfer::unpack(transfer::deserialize(transfer_vaa_payload));
+
+        let (
+            asset_info,
+            bridged_out
+        ) =
+            verify_and_bridge_out(
+                latest_only,
+                token_bridge_state,
+                token_chain,
+                token_address,
+                recipient_chain,
+                amount
+            );
+
+        let recipient = external_address::to_address(recipient);
+
+        // If the recipient did not redeem his own transfer, Token Bridge will
+        // split the withdrawn coins and send a portion to the transaction
+        // relayer.
+        let payout = if (
+            normalized_amount::value(&relayer_fee) == 0 ||
+            recipient == tx_context::sender(ctx)
+        ) {
+            balance::zero()
+        } else {
+            let payout_amount =
+                normalized_amount::to_raw(
+                    relayer_fee,
+                    token_registry::coin_decimals(&asset_info)
+                );
+            balance::split(&mut bridged_out, payout_amount)
+        };
+
+        // Transfer tokens to the recipient.
+        sui::transfer::public_transfer(
+            coin::from_balance(bridged_out, ctx),
+            recipient
+        );
+
+        // Finally produce the receipt that a relayer can consume via
+        // `redeem_relayer_payout`.
+        RelayerReceipt {
+            payout: coin::from_balance(payout, ctx)
+        }
+    }
+
+    #[test_only]
+    public fun burn<CoinType>(receipt: RelayerReceipt<CoinType>) {
+        coin::burn_for_testing(redeem_relayer_payout(receipt));
+    }
+}
+
+#[test_only]
+module token_bridge::complete_transfer_tests {
+    use sui::coin::{Self, Coin};
+    use sui::test_scenario::{Self};
+    use wormhole::state::{chain_id};
+    use wormhole::wormhole_scenario::{parse_and_verify_vaa};
+
+    use token_bridge::coin_wrapped_12::{Self, COIN_WRAPPED_12};
+    use token_bridge::coin_wrapped_7::{Self, COIN_WRAPPED_7};
+    use token_bridge::coin_native_10::{Self, COIN_NATIVE_10};
+    use token_bridge::coin_native_4::{Self, COIN_NATIVE_4};
+    use token_bridge::complete_transfer::{Self};
+    use token_bridge::dummy_message::{Self};
+    use token_bridge::native_asset::{Self};
+    use token_bridge::state::{Self};
+    use token_bridge::token_bridge_scenario::{
+        set_up_wormhole_and_token_bridge,
+        register_dummy_emitter,
+        return_state,
+        take_state,
+        three_people,
+        two_people
+    };
+    use token_bridge::token_registry::{Self};
+    use token_bridge::transfer::{Self};
+    use token_bridge::vaa::{Self};
+    use token_bridge::wrapped_asset::{Self};
+
+    struct OTHER_COIN_WITNESS has drop {}
+
+    #[test]
+    /// An end-to-end test for complete transfer native with VAA.
+    fun test_complete_transfer_native_10_relayer_fee() {
+        use token_bridge::complete_transfer::{
+            authorize_transfer,
+            redeem_relayer_payout
+        };
+
+        let transfer_vaa =
+            dummy_message::encoded_transfer_vaa_native_with_fee();
+
+        let (expected_recipient, tx_relayer, coin_deployer) = three_people();
+        let my_scenario = test_scenario::begin(tx_relayer);
+        let scenario = &mut my_scenario;
+
+        // Set up contracts.
+        let wormhole_fee = 350;
+        set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
+
+        // Register foreign emitter on chain ID == 2.
+        let expected_source_chain = 2;
+        register_dummy_emitter(scenario, expected_source_chain);
+
+        let custody_amount = 500000;
+        coin_native_10::init_register_and_deposit(
+            scenario,
+            coin_deployer,
+            custody_amount
+        );
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, tx_relayer);
+
+        let token_bridge_state = take_state(scenario);
+
+        // These will be checked later.
+        let expected_relayer_fee = 100000;
+        let expected_recipient_amount = 200000;
+        let expected_amount = expected_relayer_fee + expected_recipient_amount;
+
+        // Scope to allow immutable reference to `TokenRegistry`.
+        {
+            let registry = state::borrow_token_registry(&token_bridge_state);
+            let asset = token_registry::borrow_native<COIN_NATIVE_10>(registry);
+            assert!(native_asset::custody(asset) == custody_amount, 0);
+
+            // Verify transfer parameters.
+            let parsed =
+                transfer::deserialize_test_only(
+                    wormhole::vaa::take_payload(
+                        parse_and_verify_vaa(scenario, transfer_vaa)
+                    )
+                );
+
+            let asset_info =
+                token_registry::verified_asset<COIN_NATIVE_10>(registry);
+            let expected_token_chain = token_registry::token_chain(&asset_info);
+            let expected_token_address =
+                token_registry::token_address(&asset_info);
+            assert!(transfer::token_chain(&parsed) == expected_token_chain, 0);
+            assert!(
+                transfer::token_address(&parsed) == expected_token_address,
+                0
+            );
+
+            let coin_meta = test_scenario::take_shared(scenario);
+
+            let decimals = coin::get_decimals<COIN_NATIVE_10>(&coin_meta);
+
+            test_scenario::return_shared(coin_meta);
+
+            assert!(
+                transfer::raw_amount(&parsed, decimals) == expected_amount,
+                0
+            );
+
+            assert!(
+                transfer::raw_relayer_fee(&parsed, decimals) == expected_relayer_fee,
+                0
+            );
+            assert!(
+                transfer::recipient_as_address(&parsed) == expected_recipient,
+                0
+            );
+            assert!(transfer::recipient_chain(&parsed) == chain_id(), 0);
+
+            // Clean up.
+            transfer::destroy(parsed);
+        };
+
+        let verified_vaa = parse_and_verify_vaa(scenario, transfer_vaa);
+        let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa);
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, tx_relayer);
+
+        let receipt =
+            authorize_transfer<COIN_NATIVE_10>(
+                &mut token_bridge_state,
+                msg,
+                test_scenario::ctx(scenario)
+            );
+        let payout = redeem_relayer_payout(receipt);
+        assert!(coin::value(&payout) == expected_relayer_fee, 0);
+
+        // TODO: Check for one event? `TransferRedeemed`.
+        let _effects = test_scenario::next_tx(scenario, tx_relayer);
+
+        // Check recipient's `Coin`.
+        let received =
+            test_scenario::take_from_address<Coin<COIN_NATIVE_10>>(
+                scenario,
+                expected_recipient
+            );
+        assert!(coin::value(&received) == expected_recipient_amount, 0);
+
+        // And check remaining amount in custody.
+        let registry = state::borrow_token_registry(&token_bridge_state);
+        let remaining = custody_amount - expected_amount;
+        {
+            let asset = token_registry::borrow_native<COIN_NATIVE_10>(registry);
+            assert!(native_asset::custody(asset) == remaining, 0);
+        };
+
+        // Clean up.
+        coin::burn_for_testing(payout);
+        coin::burn_for_testing(received);
+        return_state(token_bridge_state);
+
+        // Done.
+        test_scenario::end(my_scenario);
+    }
+
+    #[test]
+    /// An end-to-end test for complete transfer native with VAA.
+    fun test_complete_transfer_native_4_relayer_fee() {
+        use token_bridge::complete_transfer::{
+            authorize_transfer,
+            redeem_relayer_payout
+        };
+
+        let transfer_vaa =
+            dummy_message::encoded_transfer_vaa_native_with_fee();
+
+        let (expected_recipient, tx_relayer, coin_deployer) = three_people();
+        let my_scenario = test_scenario::begin(tx_relayer);
+        let scenario = &mut my_scenario;
+
+        // Set up contracts.
+        let wormhole_fee = 350;
+        set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
+
+        // Register foreign emitter on chain ID == 2.
+        let expected_source_chain = 2;
+        register_dummy_emitter(scenario, expected_source_chain);
+
+        let custody_amount = 5000;
+        coin_native_4::init_register_and_deposit(
+            scenario,
+            coin_deployer,
+            custody_amount
+        );
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, tx_relayer);
+
+        let token_bridge_state = take_state(scenario);
+
+        // These will be checked later.
+        let expected_relayer_fee = 1000;
+        let expected_recipient_amount = 2000;
+        let expected_amount = expected_relayer_fee + expected_recipient_amount;
+
+        // Scope to allow immutable reference to `TokenRegistry`.
+        {
+            let registry = state::borrow_token_registry(&token_bridge_state);
+            let asset = token_registry::borrow_native<COIN_NATIVE_4>(registry);
+            assert!(native_asset::custody(asset) == custody_amount, 0);
+
+            // Verify transfer parameters.
+            let parsed =
+                transfer::deserialize_test_only(
+                    wormhole::vaa::take_payload(
+                        parse_and_verify_vaa(scenario, transfer_vaa)
+                    )
+                );
+
+            let asset_info =
+                token_registry::verified_asset<COIN_NATIVE_4>(registry);
+            let expected_token_chain = token_registry::token_chain(&asset_info);
+            let expected_token_address =
+                token_registry::token_address(&asset_info);
+            assert!(transfer::token_chain(&parsed) == expected_token_chain, 0);
+            assert!(
+                transfer::token_address(&parsed) == expected_token_address,
+                0
+            );
+
+            let coin_meta = test_scenario::take_shared(scenario);
+            let decimals = coin::get_decimals<COIN_NATIVE_4>(&coin_meta);
+            test_scenario::return_shared(coin_meta);
+
+            assert!(
+                transfer::raw_amount(&parsed, decimals) == expected_amount,
+                0
+            );
+
+            assert!(
+                transfer::raw_relayer_fee(&parsed, decimals) == expected_relayer_fee,
+                0
+            );
+            assert!(
+                transfer::recipient_as_address(&parsed) == expected_recipient,
+                0
+            );
+            assert!(transfer::recipient_chain(&parsed) == chain_id(), 0);
+
+            // Clean up.
+            transfer::destroy(parsed);
+        };
+
+        let verified_vaa = parse_and_verify_vaa(scenario, transfer_vaa);
+        let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa);
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, tx_relayer);
+
+        let receipt =
+            authorize_transfer<COIN_NATIVE_4>(
+                &mut token_bridge_state,
+                msg,
+                test_scenario::ctx(scenario)
+            );
+        let payout = redeem_relayer_payout(receipt);
+        assert!(coin::value(&payout) == expected_relayer_fee, 0);
+
+        // TODO: Check for one event? `TransferRedeemed`.
+        let _effects = test_scenario::next_tx(scenario, tx_relayer);
+
+        // Check recipient's `Coin`.
+        let received =
+            test_scenario::take_from_address<Coin<COIN_NATIVE_4>>(
+                scenario,
+                expected_recipient
+            );
+        assert!(coin::value(&received) == expected_recipient_amount, 0);
+
+        // And check remaining amount in custody.
+        let registry = state::borrow_token_registry(&token_bridge_state);
+        let remaining = custody_amount - expected_amount;
+        {
+            let asset = token_registry::borrow_native<COIN_NATIVE_4>(registry);
+            assert!(native_asset::custody(asset) == remaining, 0);
+        };
+
+        // Clean up.
+        coin::burn_for_testing(payout);
+        coin::burn_for_testing(received);
+        return_state(token_bridge_state);
+
+        // Done.
+        test_scenario::end(my_scenario);
+    }
+
+    #[test]
+    /// An end-to-end test for complete transfer wrapped with VAA.
+    fun test_complete_transfer_wrapped_7_relayer_fee() {
+        use token_bridge::complete_transfer::{
+            authorize_transfer,
+            redeem_relayer_payout
+        };
+
+        let transfer_vaa =
+            dummy_message::encoded_transfer_vaa_wrapped_7_with_fee();
+
+        let (expected_recipient, tx_relayer, coin_deployer) = three_people();
+        let my_scenario = test_scenario::begin(tx_relayer);
+        let scenario = &mut my_scenario;
+
+        // Set up contracts.
+        let wormhole_fee = 350;
+        set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
+
+        // Register foreign emitter on chain ID == 2.
+        let expected_source_chain = 2;
+        register_dummy_emitter(scenario, expected_source_chain);
+
+        coin_wrapped_7::init_and_register(scenario, coin_deployer);
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, tx_relayer);
+
+        let token_bridge_state = take_state(scenario);
+
+        // These will be checked later.
+        let expected_relayer_fee = 1000;
+        let expected_recipient_amount = 2000;
+        let expected_amount = expected_relayer_fee + expected_recipient_amount;
+
+        // Scope to allow immutable reference to `TokenRegistry`.
+        {
+            let registry = state::borrow_token_registry(&token_bridge_state);
+            let asset =
+                token_registry::borrow_wrapped<COIN_WRAPPED_7>(registry);
+            assert!(wrapped_asset::total_supply(asset) == 0, 0);
+
+            // Verify transfer parameters.
+            let parsed =
+                transfer::deserialize_test_only(
+                    wormhole::vaa::take_payload(
+                        parse_and_verify_vaa(scenario, transfer_vaa)
+                    )
+                );
+
+            let asset_info =
+                token_registry::verified_asset<COIN_WRAPPED_7>(registry);
+            let expected_token_chain = token_registry::token_chain(&asset_info);
+            let expected_token_address =
+                token_registry::token_address(&asset_info);
+            assert!(transfer::token_chain(&parsed) == expected_token_chain, 0);
+            assert!(
+                transfer::token_address(&parsed) == expected_token_address,
+                0
+            );
+
+            let coin_meta = test_scenario::take_shared(scenario);
+            let decimals = coin::get_decimals<COIN_WRAPPED_7>(&coin_meta);
+            test_scenario::return_shared(coin_meta);
+
+            assert!(
+                transfer::raw_amount(&parsed, decimals) == expected_amount,
+                0
+            );
+
+            assert!(
+                transfer::raw_relayer_fee(&parsed, decimals) == expected_relayer_fee,
+                0
+            );
+            assert!(
+                transfer::recipient_as_address(&parsed) == expected_recipient,
+                0
+            );
+            assert!(transfer::recipient_chain(&parsed) == chain_id(), 0);
+
+            // Clean up.
+            transfer::destroy(parsed);
+        };
+
+        let verified_vaa = parse_and_verify_vaa(scenario, transfer_vaa);
+        let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa);
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, tx_relayer);
+
+        let receipt =
+            authorize_transfer<COIN_WRAPPED_7>(
+                &mut token_bridge_state,
+                msg,
+                test_scenario::ctx(scenario)
+            );
+        let payout = redeem_relayer_payout(receipt);
+        assert!(coin::value(&payout) == expected_relayer_fee, 0);
+
+        // TODO: Check for one event? `TransferRedeemed`.
+        let _effects = test_scenario::next_tx(scenario, tx_relayer);
+
+        // Check recipient's `Coin`.
+        let received =
+            test_scenario::take_from_address<Coin<COIN_WRAPPED_7>>(
+                scenario,
+                expected_recipient
+            );
+        assert!(coin::value(&received) == expected_recipient_amount, 0);
+
+        // And check that the amount is the total wrapped supply.
+        let registry = state::borrow_token_registry(&token_bridge_state);
+        {
+            let asset =
+                token_registry::borrow_wrapped<COIN_WRAPPED_7>(registry);
+            assert!(wrapped_asset::total_supply(asset) == expected_amount, 0);
+        };
+
+        // Clean up.
+        coin::burn_for_testing(payout);
+        coin::burn_for_testing(received);
+        return_state(token_bridge_state);
+
+        // Done.
+        test_scenario::end(my_scenario);
+    }
+
+    #[test]
+    /// An end-to-end test for complete transfer wrapped with VAA.
+    fun test_complete_transfer_wrapped_12_relayer_fee() {
+        use token_bridge::complete_transfer::{
+            authorize_transfer,
+            redeem_relayer_payout
+        };
+
+        let transfer_vaa =
+            dummy_message::encoded_transfer_vaa_wrapped_12_with_fee();
+
+        let (expected_recipient, tx_relayer, coin_deployer) = three_people();
+        let my_scenario = test_scenario::begin(tx_relayer);
+        let scenario = &mut my_scenario;
+
+        // Set up contracts.
+        let wormhole_fee = 350;
+        set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
+
+        // Register foreign emitter on chain ID == 2.
+        let expected_source_chain = 2;
+        register_dummy_emitter(scenario, expected_source_chain);
+
+        coin_wrapped_12::init_and_register(scenario, coin_deployer);
+
+        // Ignore effects.
+        //
+        // NOTE: `tx_relayer` != `expected_recipient`.
+        assert!(expected_recipient != tx_relayer, 0);
+        test_scenario::next_tx(scenario, tx_relayer);
+
+        let token_bridge_state = take_state(scenario);
+
+        // These will be checked later.
+        let expected_relayer_fee = 1000;
+        let expected_recipient_amount = 2000;
+        let expected_amount = expected_relayer_fee + expected_recipient_amount;
+
+        // Scope to allow immutable reference to `TokenRegistry`.
+        {
+            let registry = state::borrow_token_registry(&token_bridge_state);
+            let asset =
+                token_registry::borrow_wrapped<COIN_WRAPPED_12>(registry);
+            assert!(wrapped_asset::total_supply(asset) == 0, 0);
+
+            // Verify transfer parameters.
+            let parsed =
+                transfer::deserialize_test_only(
+                    wormhole::vaa::take_payload(
+                        parse_and_verify_vaa(scenario, transfer_vaa)
+                    )
+                );
+
+            let asset_info =
+                token_registry::verified_asset<COIN_WRAPPED_12>(registry);
+            let expected_token_chain = token_registry::token_chain(&asset_info);
+            let expected_token_address =
+                token_registry::token_address(&asset_info);
+            assert!(transfer::token_chain(&parsed) == expected_token_chain, 0);
+            assert!(transfer::token_address(&parsed) == expected_token_address, 0);
+
+            let coin_meta = test_scenario::take_shared(scenario);
+            let decimals = coin::get_decimals<COIN_WRAPPED_12>(&coin_meta);
+            test_scenario::return_shared(coin_meta);
+
+            assert!(transfer::raw_amount(&parsed, decimals) == expected_amount, 0);
+
+            assert!(
+                transfer::raw_relayer_fee(&parsed, decimals) == expected_relayer_fee,
+                0
+            );
+            assert!(
+                transfer::recipient_as_address(&parsed) == expected_recipient,
+                0
+            );
+            assert!(transfer::recipient_chain(&parsed) == chain_id(), 0);
+
+            // Clean up.
+            transfer::destroy(parsed);
+        };
+
+        let verified_vaa = parse_and_verify_vaa(scenario, transfer_vaa);
+        let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa);
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, tx_relayer);
+
+        let receipt =
+            authorize_transfer<COIN_WRAPPED_12>(
+                &mut token_bridge_state,
+                msg,
+                test_scenario::ctx(scenario)
+            );
+        let payout = redeem_relayer_payout(receipt);
+        assert!(coin::value(&payout) == expected_relayer_fee, 0);
+
+        // TODO: Check for one event? `TransferRedeemed`.
+        let _effects = test_scenario::next_tx(scenario, tx_relayer);
+
+        // Check recipient's `Coin`.
+        let received =
+            test_scenario::take_from_address<Coin<COIN_WRAPPED_12>>(
+                scenario,
+                expected_recipient
+            );
+        assert!(coin::value(&received) == expected_recipient_amount, 0);
+
+        // And check that the amount is the total wrapped supply.
+        let registry = state::borrow_token_registry(&token_bridge_state);
+        {
+            let asset = token_registry::borrow_wrapped<COIN_WRAPPED_12>(registry);
+            assert!(wrapped_asset::total_supply(asset) == expected_amount, 0);
+        };
+
+        // Clean up.
+        coin::burn_for_testing(payout);
+        coin::burn_for_testing(received);
+        return_state(token_bridge_state);
+
+        // Done.
+        test_scenario::end(my_scenario);
+    }
+
+    #[test]
+    /// An end-to-end test for complete transfer native with VAA. The encoded VAA
+    /// specifies a nonzero fee, however the `recipient` should receive the full
+    /// amount for self redeeming the transfer.
+    fun test_complete_transfer_native_10_relayer_fee_self_redemption() {
+        use token_bridge::complete_transfer::{
+            authorize_transfer,
+            redeem_relayer_payout
+        };
+
+        let transfer_vaa =
+            dummy_message::encoded_transfer_vaa_native_with_fee();
+
+        let (expected_recipient, _, coin_deployer) = three_people();
+        let my_scenario = test_scenario::begin(expected_recipient);
+        let scenario = &mut my_scenario;
+
+        // Set up contracts.
+        let wormhole_fee = 350;
+        set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
+
+        // Register foreign emitter on chain ID == 2.
+        let expected_source_chain = 2;
+        register_dummy_emitter(scenario, expected_source_chain);
+
+        let custody_amount = 500000;
+        coin_native_10::init_register_and_deposit(
+            scenario,
+            coin_deployer,
+            custody_amount
+        );
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, expected_recipient);
+
+        let token_bridge_state = take_state(scenario);
+
+        // NOTE: Although there is a fee encoded in the VAA, the relayer
+        // shouldn't receive this fee. The `expected_relayer_fee` should
+        // go to the recipient.
+        //
+        // These values will be used later.
+        let expected_relayer_fee = 0;
+        let encoded_relayer_fee = 100000;
+        let expected_recipient_amount = 300000;
+        let expected_amount = expected_relayer_fee + expected_recipient_amount;
+
+        // Scope to allow immutable reference to `TokenRegistry`.
+        {
+            let registry = state::borrow_token_registry(&token_bridge_state);
+            let asset = token_registry::borrow_native<COIN_NATIVE_10>(registry);
+            assert!(native_asset::custody(asset) == custody_amount, 0);
+
+            // Verify transfer parameters.
+            let parsed =
+                transfer::deserialize_test_only(
+                    wormhole::vaa::take_payload(
+                        parse_and_verify_vaa(scenario, transfer_vaa)
+                    )
+                );
+
+            let asset_info =
+                token_registry::verified_asset<COIN_NATIVE_10>(registry);
+            let expected_token_chain = token_registry::token_chain(&asset_info);
+            let expected_token_address =
+                token_registry::token_address(&asset_info);
+            assert!(transfer::token_chain(&parsed) == expected_token_chain, 0);
+            assert!(transfer::token_address(&parsed) == expected_token_address, 0);
+
+            let coin_meta = test_scenario::take_shared(scenario);
+
+            let decimals = coin::get_decimals<COIN_NATIVE_10>(&coin_meta);
+
+            test_scenario::return_shared(coin_meta);
+
+            assert!(transfer::raw_amount(&parsed, decimals) == expected_amount, 0);
+            assert!(
+                transfer::raw_relayer_fee(&parsed, decimals) == encoded_relayer_fee,
+                0
+            );
+            assert!(
+                transfer::recipient_as_address(&parsed) == expected_recipient,
+                0
+            );
+            assert!(transfer::recipient_chain(&parsed) == chain_id(), 0);
+
+            // Clean up.
+            transfer::destroy(parsed);
+        };
+
+        let verified_vaa = parse_and_verify_vaa(scenario, transfer_vaa);
+        let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa);
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, expected_recipient);
+
+        let receipt =
+            authorize_transfer<COIN_NATIVE_10>(
+                &mut token_bridge_state,
+                msg,
+                test_scenario::ctx(scenario)
+            );
+        let payout = redeem_relayer_payout(receipt);
+        assert!(coin::value(&payout) == expected_relayer_fee, 0);
+
+        // TODO: Check for one event? `TransferRedeemed`.
+        let _effects = test_scenario::next_tx(scenario, expected_recipient);
+
+        // Check recipient's `Coin`.
+        let received =
+            test_scenario::take_from_address<Coin<COIN_NATIVE_10>>(
+                scenario,
+                expected_recipient
+            );
+        assert!(coin::value(&received) == expected_recipient_amount, 0);
+
+        // And check remaining amount in custody.
+        let registry = state::borrow_token_registry(&token_bridge_state);
+        let remaining = custody_amount - expected_amount;
+        {
+            let asset = token_registry::borrow_native<COIN_NATIVE_10>(registry);
+            assert!(native_asset::custody(asset) == remaining, 0);
+        };
+
+        // Clean up.
+        coin::burn_for_testing(payout);
+        coin::burn_for_testing(received);
+        return_state(token_bridge_state);
+
+        // Done.
+        test_scenario::end(my_scenario);
+    }
+
+    #[test]
+    #[expected_failure(
+        abort_code = complete_transfer::E_CANONICAL_TOKEN_INFO_MISMATCH
+    )]
+    /// This test verifies that `authorize_transfer` reverts when called with
+    /// a native COIN_TYPE that's not encoded in the VAA.
+    fun test_cannot_authorize_transfer_native_invalid_coin_type() {
+        use token_bridge::complete_transfer::{authorize_transfer};
+
+        let transfer_vaa =
+            dummy_message::encoded_transfer_vaa_native_with_fee();
+
+        let (_, tx_relayer, coin_deployer) = three_people();
+        let my_scenario = test_scenario::begin(tx_relayer);
+        let scenario = &mut my_scenario;
+
+        // Set up contracts.
+        let wormhole_fee = 350;
+        set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
+
+        // Register foreign emitter on chain ID == 2.
+        let expected_source_chain = 2;
+        register_dummy_emitter(scenario, expected_source_chain);
+
+        let custody_amount_coin_10 = 500000;
+        coin_native_10::init_register_and_deposit(
+            scenario,
+            coin_deployer,
+            custody_amount_coin_10
+        );
+
+        // Register a second native asset.
+        let custody_amount_coin_4 = 69420;
+        coin_native_4::init_register_and_deposit(
+            scenario,
+            coin_deployer,
+            custody_amount_coin_4
+        );
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, tx_relayer);
+
+        let token_bridge_state = take_state(scenario);
+
+        // Scope to allow immutable reference to `TokenRegistry`. This verifies
+        // that both coin types have been registered.
+        {
+            let registry = state::borrow_token_registry(&token_bridge_state);
+
+            // COIN_10.
+            let coin_10 =
+                token_registry::borrow_native<COIN_NATIVE_10>(registry);
+            assert!(
+                native_asset::custody(coin_10) == custody_amount_coin_10,
+                0
+            );
+
+            // COIN_4.
+            let coin_4 = token_registry::borrow_native<COIN_NATIVE_4>(registry);
+            assert!(native_asset::custody(coin_4) == custody_amount_coin_4, 0);
+        };
+
+        let verified_vaa = parse_and_verify_vaa(scenario, transfer_vaa);
+        let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa);
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, tx_relayer);
+
+        // NOTE: this call should revert since the transfer VAA is for
+        // a coin of type COIN_NATIVE_10. However, the `complete_transfer`
+        // method is called using the COIN_NATIVE_4 type.
+        let receipt =
+            authorize_transfer<COIN_NATIVE_4>(
+                &mut token_bridge_state,
+                msg,
+                test_scenario::ctx(scenario)
+            );
+
+        // Clean up.
+        complete_transfer::burn(receipt);
+
+        abort 42
+    }
+
+    #[test]
+    #[expected_failure(
+        abort_code = complete_transfer::E_CANONICAL_TOKEN_INFO_MISMATCH
+    )]
+    /// This test verifies that `authorize_transfer` reverts when called with
+    /// a wrapped COIN_TYPE that's not encoded in the VAA.
+    fun test_cannot_authorize_transfer_wrapped_invalid_coin_type() {
+        use token_bridge::complete_transfer::{authorize_transfer};
+
+        let transfer_vaa = dummy_message::encoded_transfer_vaa_wrapped_12_with_fee();
+
+        let (expected_recipient, tx_relayer, coin_deployer) = three_people();
+        let my_scenario = test_scenario::begin(tx_relayer);
+        let scenario = &mut my_scenario;
+
+        // Set up contracts.
+        let wormhole_fee = 350;
+        set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
+
+        // Register foreign emitter on chain ID == 2.
+        let expected_source_chain = 2;
+        register_dummy_emitter(scenario, expected_source_chain);
+
+        // Register both wrapped coin types (12 and 7).
+        coin_wrapped_12::init_and_register(scenario, coin_deployer);
+        coin_wrapped_7::init_and_register(scenario, coin_deployer);
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, tx_relayer);
+
+        // NOTE: `tx_relayer` != `expected_recipient`.
+        assert!(expected_recipient != tx_relayer, 0);
+
+        let token_bridge_state = take_state(scenario);
+
+        // Scope to allow immutable reference to `TokenRegistry`. This verifies
+        // that both coin types have been registered.
+        {
+            let registry = state::borrow_token_registry(&token_bridge_state);
+
+            let coin_12 =
+                token_registry::borrow_wrapped<COIN_WRAPPED_12>(registry);
+            assert!(wrapped_asset::total_supply(coin_12) == 0, 0);
+
+            let coin_7 =
+                token_registry::borrow_wrapped<COIN_WRAPPED_7>(registry);
+            assert!(wrapped_asset::total_supply(coin_7) == 0, 0);
+        };
+
+        let verified_vaa = parse_and_verify_vaa(scenario, transfer_vaa);
+        let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa);
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, tx_relayer);
+
+        // NOTE: this call should revert since the transfer VAA is for
+        // a coin of type COIN_WRAPPED_12. However, the `authorize_transfer`
+        // method is called using the COIN_WRAPPED_7 type.
+        let receipt =
+            authorize_transfer<COIN_WRAPPED_7>(
+                &mut token_bridge_state,
+                msg,
+                test_scenario::ctx(scenario)
+            );
+
+        // Clean up.
+        complete_transfer::burn(receipt);
+
+        abort 42
+    }
+
+    #[test]
+    #[expected_failure(abort_code = complete_transfer::E_TARGET_NOT_SUI)]
+    /// This test verifies that `authorize_transfer` reverts when a transfer is
+    /// sent to the wrong target blockchain (chain ID != 21).
+    fun test_cannot_authorize_transfer_wrapped_12_invalid_target_chain() {
+        use token_bridge::complete_transfer::{authorize_transfer};
+
+        let transfer_vaa =
+            dummy_message::encoded_transfer_vaa_wrapped_12_invalid_target_chain();
+
+        let (expected_recipient, tx_relayer, coin_deployer) = three_people();
+        let my_scenario = test_scenario::begin(tx_relayer);
+        let scenario = &mut my_scenario;
+
+        // Set up contracts.
+        let wormhole_fee = 350;
+        set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
+
+        // Register foreign emitter on chain ID == 2.
+        let expected_source_chain = 2;
+        register_dummy_emitter(scenario, expected_source_chain);
+
+        coin_wrapped_12::init_and_register(scenario, coin_deployer);
+
+        // Ignore effects.
+        //
+        // NOTE: `tx_relayer` != `expected_recipient`.
+        assert!(expected_recipient != tx_relayer, 0);
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, tx_relayer);
+
+        let token_bridge_state = take_state(scenario);
+
+        let verified_vaa = parse_and_verify_vaa(scenario, transfer_vaa);
+        let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa);
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, tx_relayer);
+
+        // NOTE: this call should revert since the target chain encoded is
+        // chain 69 instead of chain 21 (Sui).
+        let receipt =
+            authorize_transfer<COIN_WRAPPED_12>(
+                &mut token_bridge_state,
+                msg,
+                test_scenario::ctx(scenario)
+            );
+
+        // Clean up.
+        complete_transfer::burn(receipt);
+
+        abort 42
+    }
+
+    #[test]
+    #[expected_failure(abort_code = wormhole::package_utils::E_NOT_CURRENT_VERSION)]
+    fun test_cannot_complete_transfer_outdated_version() {
+        use token_bridge::complete_transfer::{authorize_transfer};
+
+        let transfer_vaa =
+            dummy_message::encoded_transfer_vaa_native_with_fee();
+
+        let (tx_relayer, coin_deployer) = two_people();
+        let my_scenario = test_scenario::begin(tx_relayer);
+        let scenario = &mut my_scenario;
+
+        // Set up contracts.
+        let wormhole_fee = 350;
+        set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
+
+        // Register foreign emitter on chain ID == 2.
+        let expected_source_chain = 2;
+        register_dummy_emitter(scenario, expected_source_chain);
+
+        let custody_amount = 500000;
+        coin_native_10::init_register_and_deposit(
+            scenario,
+            coin_deployer,
+            custody_amount
+        );
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, tx_relayer);
+
+        let token_bridge_state = take_state(scenario);
+
+        let verified_vaa = parse_and_verify_vaa(scenario, transfer_vaa);
+        let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa);
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, tx_relayer);
+
+        // Conveniently roll version back.
+        state::reverse_migrate_version(&mut token_bridge_state);
+
+        // Simulate executing with an outdated build by upticking the minimum
+        // required version for `publish_message` to something greater than
+        // this build.
+        state::migrate_version_test_only(
+            &mut token_bridge_state,
+            token_bridge::version_control::previous_version_test_only(),
+            token_bridge::version_control::next_version()
+        );
+
+        // You shall not pass!
+        let receipt =
+            authorize_transfer<COIN_NATIVE_10>(
+                &mut token_bridge_state,
+                msg,
+                test_scenario::ctx(scenario)
+            );
+
+        // Clean up.
+        complete_transfer::burn(receipt);
+
+        abort 42
+    }
+}

+ 776 - 0
target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/complete_transfer_with_payload.move

@@ -0,0 +1,776 @@
+// SPDX-License-Identifier: Apache 2
+
+/// This module implements two methods: `authorize_transfer` and `redeem_coin`,
+/// which are to be executed in a transaction block in this order.
+///
+/// `authorize_transfer` allows a contract to complete a Token Bridge transfer
+/// with arbitrary payload. This deserialized `TransferWithPayload` with the
+/// bridged balance and source chain ID are packaged in a `RedeemerReceipt`.
+///
+/// `redeem_coin` unpacks the `RedeemerReceipt` and checks whether the specified
+/// `EmitterCap` is the specified redeemer for this transfer. If he is the
+/// correct redeemer, the balance is unpacked and transformed into `Coin` and
+/// is returned alongside `TransferWithPayload` and source chain ID.
+///
+/// The purpose of splitting this transfer redemption into two steps is in case
+/// Token Bridge needs to be upgraded and there is a breaking change for this
+/// module, an integrator would not be left broken. It is discouraged to put
+/// `authorize_transfer` in an integrator's package logic. Otherwise, this
+/// integrator needs to be prepared to upgrade his contract to handle the latest
+/// version of `complete_transfer_with_payload`.
+///
+/// Instead, an integrator is encouraged to execute a transaction block, which
+/// executes `authorize_transfer` using the latest Token Bridge package ID and
+/// to implement `redeem_coin` in his contract to consume this receipt. This is
+/// similar to how an integrator with Wormhole is not meant to use
+/// `vaa::parse_and_verify` in his contract in case the `vaa` module needs to
+/// be upgraded due to a breaking change.
+///
+/// Like in `complete_transfer`, a VAA with an encoded transfer can be redeemed
+/// only once.
+///
+/// See `transfer_with_payload` module for serialization and deserialization of
+/// Wormhole message payload.
+module token_bridge::complete_transfer_with_payload {
+    use sui::coin::{Self, Coin};
+    use sui::object::{Self};
+    use sui::tx_context::{TxContext};
+    use wormhole::emitter::{EmitterCap};
+
+    use token_bridge::complete_transfer::{Self};
+    use token_bridge::state::{Self, State, LatestOnly};
+    use token_bridge::transfer_with_payload::{Self, TransferWithPayload};
+    use token_bridge::vaa::{Self, TokenBridgeMessage};
+
+    /// `EmitterCap` address does not agree with encoded redeemer.
+    const E_INVALID_REDEEMER: u64 = 0;
+
+    #[allow(lint(coin_field))]
+    /// This type is only generated from `authorize_transfer` and can only be
+    /// redeemed using `redeem_coin`. Integrators are expected to implement
+    /// `redeem_coin` within their contracts and call `authorize_transfer` in a
+    /// transaction block preceding the method that consumes this receipt. The
+    /// only way to destroy this receipt is calling `redeem_coin` with an
+    /// `EmitterCap` generated from the `wormhole::emitter` module, whose ID is
+    /// the expected redeemer for this token transfer.
+    struct RedeemerReceipt<phantom CoinType> {
+        /// Which chain ID this transfer originated from.
+        source_chain: u16,
+        /// Deserialized transfer info.
+        parsed: TransferWithPayload,
+        /// Coin of bridged asset.
+        bridged_out: Coin<CoinType>
+    }
+
+    /// `authorize_transfer` deserializes a token transfer VAA payload, which
+    /// encodes its own arbitrary payload (which has meaning to the redeemer).
+    /// Once the transfer is authorized, an event (`TransferRedeemed`) is
+    /// emitted to reflect which Token Bridge this transfer originated from.
+    /// The `RedeemerReceipt` returned wraps a balance reflecting the encoded
+    /// transfer amount along with the source chain and deserialized
+    /// `TransferWithPayload`.
+    ///
+    /// NOTE: This method is guarded by a minimum build version check. This
+    /// method could break backward compatibility on an upgrade.
+    ///
+    /// It is important for integrators to refrain from calling this method
+    /// within their contracts. This method is meant to be called in a
+    /// transaction block, passing the `RedeemerReceipt` to a method which calls
+    /// `redeem_coin` within a contract. If in a circumstance where this module
+    /// has a breaking change in an upgrade, `redeem_coin` will not be affected
+    /// by this change.
+    ///
+    /// See `redeem_coin` for more details.
+    public fun authorize_transfer<CoinType>(
+        token_bridge_state: &mut State,
+        msg: TokenBridgeMessage,
+        ctx: &mut TxContext
+    ): RedeemerReceipt<CoinType> {
+        // This capability ensures that the current build version is used.
+        let latest_only = state::assert_latest_only(token_bridge_state);
+
+        // Emitting the transfer being redeemed.
+        //
+        // NOTE: We save the emitter chain ID to save the integrator from
+        // having to `parse_and_verify` the same encoded VAA to get this info.
+        let source_chain =
+            complete_transfer::emit_transfer_redeemed(&msg);
+
+        // Finally deserialize the Wormhole message payload and handle bridging
+        // out token of a given coin type.
+        handle_authorize_transfer(
+            &latest_only,
+            token_bridge_state,
+            source_chain,
+            vaa::take_payload(msg),
+            ctx
+        )
+    }
+
+    /// After a transfer is authorized, only a valid redeemer may unpack the
+    /// `RedeemerReceipt`. The specified `EmitterCap` is the only authorized
+    /// redeemer of the transfer. Once the redeemer is validated, coin from
+    /// this receipt of the specified coin type is returned alongside the
+    /// deserialized `TransferWithPayload` and source chain ID.
+    ///
+    /// NOTE: Integrators of Token Bridge redeeming these token transfers should
+    /// be calling only this method from their contracts. This method is not
+    /// guarded by version control (thus not requiring a reference to the
+    /// Token Bridge `State` object), so it is intended to work for any package
+    /// version.
+    public fun redeem_coin<CoinType>(
+        emitter_cap: &EmitterCap,
+        receipt: RedeemerReceipt<CoinType>
+    ): (
+        Coin<CoinType>,
+        TransferWithPayload,
+        u16 // `wormhole::vaa::emitter_chain`
+    ) {
+        let RedeemerReceipt { source_chain, parsed, bridged_out } = receipt;
+
+        // Transfer must be redeemed by the contract's registered Wormhole
+        // emitter.
+        let redeemer = transfer_with_payload::redeemer_id(&parsed);
+        assert!(redeemer == object::id(emitter_cap), E_INVALID_REDEEMER);
+
+        // Create coin from balance and return other unpacked members of receipt.
+        (bridged_out, parsed, source_chain)
+    }
+
+    fun handle_authorize_transfer<CoinType>(
+        latest_only: &LatestOnly,
+        token_bridge_state: &mut State,
+        source_chain: u16,
+        transfer_vaa_payload: vector<u8>,
+        ctx: &mut TxContext
+    ): RedeemerReceipt<CoinType> {
+        // Deserialize for processing.
+        let parsed = transfer_with_payload::deserialize(transfer_vaa_payload);
+
+        // Handle bridging assets out to be returned to method caller.
+        //
+        // See `complete_transfer` module for more info.
+        let (
+            _,
+            bridged_out,
+        ) =
+            complete_transfer::verify_and_bridge_out(
+                latest_only,
+                token_bridge_state,
+                transfer_with_payload::token_chain(&parsed),
+                transfer_with_payload::token_address(&parsed),
+                transfer_with_payload::redeemer_chain(&parsed),
+                transfer_with_payload::amount(&parsed)
+            );
+
+        RedeemerReceipt {
+            source_chain,
+            parsed,
+            bridged_out: coin::from_balance(bridged_out, ctx)
+        }
+    }
+
+    #[test_only]
+    public fun burn<CoinType>(receipt: RedeemerReceipt<CoinType>) {
+        let RedeemerReceipt {
+            source_chain: _,
+            parsed: _,
+            bridged_out
+        } = receipt;
+        coin::burn_for_testing(bridged_out);
+    }
+}
+
+#[test_only]
+module token_bridge::complete_transfer_with_payload_tests {
+    use sui::coin::{Self};
+    use sui::object::{Self};
+    use sui::test_scenario::{Self};
+    use wormhole::emitter::{Self};
+    use wormhole::state::{chain_id};
+    use wormhole::wormhole_scenario::{new_emitter, parse_and_verify_vaa};
+
+    use token_bridge::coin_wrapped_12::{Self, COIN_WRAPPED_12};
+    use token_bridge::complete_transfer_with_payload::{Self};
+    use token_bridge::complete_transfer::{Self};
+    use token_bridge::coin_native_10::{Self, COIN_NATIVE_10};
+    use token_bridge::dummy_message::{Self};
+    use token_bridge::native_asset::{Self};
+    use token_bridge::state::{Self};
+    use token_bridge::token_bridge_scenario::{
+        register_dummy_emitter,
+        return_state,
+        set_up_wormhole_and_token_bridge,
+        take_state,
+        two_people
+    };
+    use token_bridge::token_registry::{Self};
+    use token_bridge::transfer_with_payload::{Self};
+    use token_bridge::vaa::{Self};
+    use token_bridge::wrapped_asset::{Self};
+
+    #[test]
+    /// Test the public-facing function authorize_transfer.
+    /// using a native transfer VAA_ATTESTED_DECIMALS_10.
+    fun test_complete_transfer_with_payload_native_asset() {
+        use token_bridge::complete_transfer_with_payload::{
+            authorize_transfer,
+            redeem_coin
+        };
+
+        let transfer_vaa =
+            dummy_message::encoded_transfer_with_payload_vaa_native();
+
+        let (user, coin_deployer) = two_people();
+        let my_scenario = test_scenario::begin(user);
+        let scenario = &mut my_scenario;
+
+        // Initialize Wormhole and Token Bridge.
+        let wormhole_fee = 350;
+        set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
+
+        // Register Sui as a foreign emitter.
+        let expected_source_chain = 2;
+        register_dummy_emitter(scenario, expected_source_chain);
+
+        // Initialize native token.
+        let mint_amount = 1000000;
+        coin_native_10::init_register_and_deposit(
+            scenario,
+            coin_deployer,
+            mint_amount
+        );
+
+        // Ignore effects. Begin processing as arbitrary tx executor.
+        test_scenario::next_tx(scenario, user);
+
+        let token_bridge_state = take_state(scenario);
+
+        {
+            let asset = token_registry::borrow_native<COIN_NATIVE_10>(
+                state::borrow_token_registry(&token_bridge_state)
+            );
+            assert!(native_asset::custody(asset) == mint_amount, 0);
+        };
+
+        // Set up dummy `EmitterCap` as the expected redeemer.
+        let emitter_cap = emitter::dummy();
+
+        // Verify that the emitter cap is the expected redeemer.
+        let expected_transfer =
+            transfer_with_payload::deserialize(
+                wormhole::vaa::take_payload(
+                    parse_and_verify_vaa(scenario, transfer_vaa)
+                )
+            );
+        assert!(
+            transfer_with_payload::redeemer_id(&expected_transfer) == object::id(&emitter_cap),
+            0
+        );
+
+        let verified_vaa = parse_and_verify_vaa(scenario, transfer_vaa);
+        let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa);
+
+        // Ignore effects. Begin processing as arbitrary tx executor.
+        test_scenario::next_tx(scenario, user);
+
+        // Execute authorize_transfer.
+        let receipt =
+            authorize_transfer<COIN_NATIVE_10>(
+                &mut token_bridge_state,
+                msg,
+                test_scenario::ctx(scenario)
+            );
+        let (
+            bridged,
+            parsed_transfer,
+            source_chain
+        ) = redeem_coin(&emitter_cap, receipt);
+
+        assert!(source_chain == expected_source_chain, 0);
+
+        // Assert coin value, source chain, and parsed transfer details are correct.
+        // We expect the coin value to be 300000, because that's in terms of
+        // 10 decimals. The amount specified in the VAA_ATTESTED_DECIMALS_12 is 3000, because that's
+        // in terms of 8 decimals.
+        let expected_bridged = 300000;
+        assert!(coin::value(&bridged) == expected_bridged, 0);
+
+        // Amount left on custody should be whatever is left remaining after
+        // the transfer.
+        let remaining = mint_amount - expected_bridged;
+        {
+            let asset = token_registry::borrow_native<COIN_NATIVE_10>(
+                state::borrow_token_registry(&token_bridge_state)
+            );
+            assert!(native_asset::custody(asset) == remaining, 0);
+        };
+
+        // Verify token info.
+        let registry = state::borrow_token_registry(&token_bridge_state);
+        let verified =
+            token_registry::verified_asset<COIN_NATIVE_10>(registry);
+        let expected_token_chain = token_registry::token_chain(&verified);
+        let expected_token_address = token_registry::token_address(&verified);
+        assert!(expected_token_chain == chain_id(), 0);
+        assert!(
+            transfer_with_payload::token_chain(&parsed_transfer) == expected_token_chain,
+            0
+        );
+        assert!(
+            transfer_with_payload::token_address(&parsed_transfer) == expected_token_address,
+            0
+        );
+
+        // Verify transfer by serializing both parsed and expected.
+        let serialized = transfer_with_payload::serialize(parsed_transfer);
+        let expected_serialized =
+            transfer_with_payload::serialize(expected_transfer);
+        assert!(serialized == expected_serialized, 0);
+
+        // Clean up.
+        return_state(token_bridge_state);
+        coin::burn_for_testing(bridged);
+        emitter::destroy_test_only(emitter_cap);
+
+        // Done.
+        test_scenario::end(my_scenario);
+    }
+
+    #[test]
+    /// Test the public-facing functions `authorize_transfer` and `redeem_coin`.
+    /// Use an actual devnet Wormhole complete transfer with payload
+    /// VAA_ATTESTED_DECIMALS_12.
+    ///
+    /// This test confirms that:
+    ///   - `authorize_transfer` with `redeem_coin` deserializes the encoded
+    ///      transfer and recovers the source chain, payload, and additional
+    ///      transfer details wrapped in a redeemer receipt.
+    ///   - a wrapped coin with the correct value is minted by the bridge
+    ///     and returned by authorize_transfer
+    ///
+    fun test_complete_transfer_with_payload_wrapped_asset() {
+        use token_bridge::complete_transfer_with_payload::{
+            authorize_transfer,
+            redeem_coin
+        };
+
+        let transfer_vaa =
+            dummy_message::encoded_transfer_with_payload_wrapped_12();
+
+        let (user, coin_deployer) = two_people();
+        let my_scenario = test_scenario::begin(user);
+        let scenario = &mut my_scenario;
+
+        // Initialize Wormhole and Token Bridge.
+        let wormhole_fee = 350;
+        set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
+
+        // Register chain ID 2 as a foreign emitter.
+        let expected_source_chain = 2;
+        register_dummy_emitter(scenario, expected_source_chain);
+
+        // Register wrapped token.
+        coin_wrapped_12::init_and_register(scenario, coin_deployer);
+
+        // Ignore effects. Begin processing as arbitrary tx executor.
+        test_scenario::next_tx(scenario, user);
+
+        let token_bridge_state = take_state(scenario);
+
+        // Set up dummy `EmitterCap` as the expected redeemer.
+        let emitter_cap = emitter::dummy();
+
+        // Verify that the emitter cap is the expected redeemer.
+        let expected_transfer =
+            transfer_with_payload::deserialize(
+                wormhole::vaa::take_payload(
+                    parse_and_verify_vaa(scenario, transfer_vaa)
+                )
+            );
+        assert!(
+            transfer_with_payload::redeemer_id(&expected_transfer) == object::id(&emitter_cap),
+            0
+        );
+
+        let verified_vaa = parse_and_verify_vaa(scenario, transfer_vaa);
+        let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa);
+
+        // Ignore effects. Begin processing as arbitrary tx executor.
+        test_scenario::next_tx(scenario, user);
+
+        // Execute authorize_transfer.
+        let receipt =
+            authorize_transfer<COIN_WRAPPED_12>(
+                &mut token_bridge_state,
+                msg,
+                test_scenario::ctx(scenario)
+            );
+        let (
+            bridged,
+            parsed_transfer,
+            source_chain
+        ) = redeem_coin(&emitter_cap, receipt);
+        assert!(source_chain == expected_source_chain, 0);
+
+        // Assert coin value, source chain, and parsed transfer details are correct.
+        let expected_bridged = 3000;
+        assert!(coin::value(&bridged) == expected_bridged, 0);
+
+        // Total supply should equal the amount just minted.
+        let registry = state::borrow_token_registry(&token_bridge_state);
+        {
+            let asset =
+                token_registry::borrow_wrapped<COIN_WRAPPED_12>(registry);
+            assert!(wrapped_asset::total_supply(asset) == expected_bridged, 0);
+        };
+
+        // Verify token info.
+        let verified =
+            token_registry::verified_asset<COIN_WRAPPED_12>(registry);
+        let expected_token_chain = token_registry::token_chain(&verified);
+        let expected_token_address = token_registry::token_address(&verified);
+        assert!(expected_token_chain != chain_id(), 0);
+        assert!(
+            transfer_with_payload::token_chain(&parsed_transfer) == expected_token_chain,
+            0
+        );
+        assert!(
+            transfer_with_payload::token_address(&parsed_transfer) == expected_token_address,
+            0
+        );
+
+        // Verify transfer by serializing both parsed and expected.
+        let serialized = transfer_with_payload::serialize(parsed_transfer);
+        let expected_serialized =
+            transfer_with_payload::serialize(expected_transfer);
+        assert!(serialized == expected_serialized, 0);
+
+        // Clean up.
+        return_state(token_bridge_state);
+        coin::burn_for_testing(bridged);
+        emitter::destroy_test_only(emitter_cap);
+
+        // Done.
+        test_scenario::end(my_scenario);
+    }
+
+    #[test]
+    #[expected_failure(
+        abort_code = complete_transfer_with_payload::E_INVALID_REDEEMER,
+    )]
+    /// Test the public-facing function authorize_transfer.
+    /// This test fails because the ecmitter_cap (recipient) is incorrect (0x2 instead of 0x3).
+    ///
+    fun test_cannot_complete_transfer_with_payload_invalid_redeemer() {
+        use token_bridge::complete_transfer_with_payload::{
+            authorize_transfer,
+            redeem_coin
+        };
+
+        let transfer_vaa =
+            dummy_message::encoded_transfer_with_payload_wrapped_12();
+
+        let (user, coin_deployer) = two_people();
+        let my_scenario = test_scenario::begin(user);
+        let scenario = &mut my_scenario;
+
+        // Initialize Wormhole and Token Bridge.
+        let wormhole_fee = 350;
+        set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
+
+        // Register chain ID 2 as a foreign emitter.
+        register_dummy_emitter(scenario, 2);
+
+        // Register wrapped asset with 12 decimals.
+        coin_wrapped_12::init_and_register(scenario, coin_deployer);
+
+        // Ignore effects. Begin processing as arbitrary tx executor.
+        test_scenario::next_tx(scenario, user);
+
+        let token_bridge_state = take_state(scenario);
+
+        let parsed =
+            transfer_with_payload::deserialize(
+                wormhole::vaa::take_payload(
+                    parse_and_verify_vaa(scenario, transfer_vaa)
+                )
+            );
+
+        // Because the vaa expects the dummy emitter as the redeemer, we need
+        // to generate another emitter.
+        let emitter_cap = new_emitter(scenario);
+
+        // Ignore effects. Begin processing as arbitrary tx executor.
+        test_scenario::next_tx(scenario, user);
+
+        assert!(
+            transfer_with_payload::redeemer_id(&parsed) != object::id(&emitter_cap),
+            0
+        );
+
+        let verified_vaa = parse_and_verify_vaa(scenario, transfer_vaa);
+        let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa);
+
+        // Ignore effects. Begin processing as arbitrary tx executor.
+        test_scenario::next_tx(scenario, user);
+
+        let receipt =
+            authorize_transfer<COIN_WRAPPED_12>(
+                &mut token_bridge_state,
+                msg,
+                test_scenario::ctx(scenario)
+            );
+        // You shall not pass!
+        let (
+            bridged_out,
+            _,
+            _
+        ) = redeem_coin(&emitter_cap, receipt);
+
+        // Clean up.
+        coin::burn_for_testing(bridged_out);
+
+        abort 42
+    }
+
+    #[test]
+    #[expected_failure(
+        abort_code = complete_transfer::E_CANONICAL_TOKEN_INFO_MISMATCH
+    )]
+    /// This test demonstrates that the `CoinType` specified for the token
+    /// redemption must agree with the canonical token info encoded in the VAA_ATTESTED_DECIMALS_12,
+    /// which is registered with the Token Bridge.
+    fun test_cannot_complete_transfer_with_payload_wrong_coin_type() {
+        use token_bridge::complete_transfer_with_payload::{
+            authorize_transfer
+        };
+
+        let transfer_vaa =
+            dummy_message::encoded_transfer_with_payload_wrapped_12();
+
+        let (user, coin_deployer) = two_people();
+        let my_scenario = test_scenario::begin(user);
+        let scenario = &mut my_scenario;
+
+        // Initialize Wormhole and Token Bridge.
+        let wormhole_fee = 350;
+        set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
+
+        // Register chain ID 2 as a foreign emitter.
+        let expected_source_chain = 2;
+        register_dummy_emitter(scenario, expected_source_chain);
+
+        // Register wrapped token.
+        coin_wrapped_12::init_and_register(scenario, coin_deployer);
+
+        // Also register unexpected token (in this case a native one).
+        coin_native_10::init_and_register(scenario, coin_deployer);
+
+        // Ignore effects. Begin processing as arbitrary tx executor.
+        test_scenario::next_tx(scenario, user);
+
+        let token_bridge_state = take_state(scenario);
+
+        let registry = state::borrow_token_registry(&token_bridge_state);
+
+        // Set up dummy `EmitterCap` as the expected redeemer.
+        let emitter_cap = emitter::dummy();
+
+        // Verify that the emitter cap is the expected redeemer.
+        let expected_transfer =
+            transfer_with_payload::deserialize(
+                wormhole::vaa::take_payload(
+                    parse_and_verify_vaa(scenario, transfer_vaa)
+                )
+            );
+        assert!(
+            transfer_with_payload::redeemer_id(&expected_transfer) == object::id(&emitter_cap),
+            0
+        );
+
+        // Also verify that the encoded token info disagrees with the expected
+        // token info.
+        let verified =
+            token_registry::verified_asset<COIN_NATIVE_10>(registry);
+        let expected_token_chain = token_registry::token_chain(&verified);
+        let expected_token_address = token_registry::token_address(&verified);
+        assert!(
+            transfer_with_payload::token_chain(&expected_transfer) != expected_token_chain,
+            0
+        );
+        assert!(
+            transfer_with_payload::token_address(&expected_transfer) != expected_token_address,
+            0
+        );
+
+        let verified_vaa = parse_and_verify_vaa(scenario, transfer_vaa);
+        let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa);
+
+        // Ignore effects. Begin processing as arbitrary tx executor.
+        test_scenario::next_tx(scenario, user);
+
+        // You shall not pass!
+        let receipt =
+            authorize_transfer<COIN_NATIVE_10>(
+                &mut token_bridge_state,
+                msg,
+                test_scenario::ctx(scenario)
+            );
+
+        // Clean up.
+        return_state(token_bridge_state);
+        complete_transfer_with_payload::burn(receipt);
+        emitter::destroy_test_only(emitter_cap);
+
+        // Done.
+        test_scenario::end(my_scenario);
+    }
+
+    #[test]
+    #[expected_failure(abort_code = complete_transfer::E_TARGET_NOT_SUI)]
+    /// This test verifies that `complete_transfer` reverts when a transfer is
+    /// sent to the wrong target blockchain (chain ID != 21).
+    fun test_cannot_complete_transfer_with_payload_wrapped_asset_invalid_target_chain() {
+        use token_bridge::complete_transfer_with_payload::{
+            authorize_transfer
+        };
+
+        let transfer_vaa =
+            dummy_message::encoded_transfer_with_payload_wrapped_12_invalid_target_chain();
+
+        let (user, coin_deployer) = two_people();
+        let my_scenario = test_scenario::begin(user);
+        let scenario = &mut my_scenario;
+
+        // Initialize Wormhole and Token Bridge.
+        let wormhole_fee = 350;
+        set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
+
+        // Register chain ID 2 as a foreign emitter.
+        let expected_source_chain = 2;
+        register_dummy_emitter(scenario, expected_source_chain);
+
+        // Register wrapped token.
+        coin_wrapped_12::init_and_register(scenario, coin_deployer);
+
+        // Ignore effects. Begin processing as arbitrary tx executor.
+        test_scenario::next_tx(scenario, user);
+
+        let token_bridge_state = take_state(scenario);
+
+        // Set up dummy `EmitterCap` as the expected redeemer.
+        let emitter_cap = emitter::dummy();
+
+        // Verify that the emitter cap is the expected redeemer.
+        let expected_transfer =
+            transfer_with_payload::deserialize(
+                wormhole::vaa::take_payload(
+                    parse_and_verify_vaa(scenario, transfer_vaa)
+                )
+            );
+        assert!(
+            transfer_with_payload::redeemer_id(&expected_transfer) == object::id(&emitter_cap),
+            0
+        );
+
+        let verified_vaa = parse_and_verify_vaa(scenario, transfer_vaa);
+        let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa);
+
+        // Ignore effects. Begin processing as arbitrary tx executor.
+        test_scenario::next_tx(scenario, user);
+
+        // You shall not pass!
+        let receipt =
+            authorize_transfer<COIN_WRAPPED_12>(
+                &mut token_bridge_state,
+                msg,
+                test_scenario::ctx(scenario)
+            );
+
+        // Clean up.
+        complete_transfer_with_payload::burn(receipt);
+
+        abort 42
+    }
+
+    #[test]
+    #[expected_failure(abort_code = wormhole::package_utils::E_NOT_CURRENT_VERSION)]
+    fun test_cannot_complete_transfer_with_payload_outdated_version() {
+        use token_bridge::complete_transfer_with_payload::{authorize_transfer};
+
+        let transfer_vaa =
+            dummy_message::encoded_transfer_with_payload_vaa_native();
+
+        let (user, coin_deployer) = two_people();
+        let my_scenario = test_scenario::begin(user);
+        let scenario = &mut my_scenario;
+
+        // Initialize Wormhole and Token Bridge.
+        let wormhole_fee = 350;
+        set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
+
+        // Register Sui as a foreign emitter.
+        let expected_source_chain = 2;
+        register_dummy_emitter(scenario, expected_source_chain);
+
+        // Initialize native token.
+        let mint_amount = 1000000;
+        coin_native_10::init_register_and_deposit(
+            scenario,
+            coin_deployer,
+            mint_amount
+        );
+
+        // Ignore effects. Begin processing as arbitrary tx executor.
+        test_scenario::next_tx(scenario, user);
+
+        let token_bridge_state = take_state(scenario);
+
+        // Set up dummy `EmitterCap` as the expected redeemer.
+        let emitter_cap = emitter::dummy();
+
+        // Verify that the emitter cap is the expected redeemer.
+        let expected_transfer =
+            transfer_with_payload::deserialize(
+                wormhole::vaa::take_payload(
+                    parse_and_verify_vaa(scenario, transfer_vaa)
+                )
+            );
+        assert!(
+            transfer_with_payload::redeemer_id(&expected_transfer) == object::id(&emitter_cap),
+            0
+        );
+
+        let verified_vaa = parse_and_verify_vaa(scenario, transfer_vaa);
+        let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa);
+
+        // Ignore effects. Begin processing as arbitrary tx executor.
+        test_scenario::next_tx(scenario, user);
+
+        // Conveniently roll version back.
+        state::reverse_migrate_version(&mut token_bridge_state);
+
+        // Simulate executing with an outdated build by upticking the minimum
+        // required version for `publish_message` to something greater than
+        // this build.
+        state::migrate_version_test_only(
+            &mut token_bridge_state,
+            token_bridge::version_control::previous_version_test_only(),
+            token_bridge::version_control::next_version()
+        );
+
+        // You shall not pass!
+        let receipt =
+            authorize_transfer<COIN_NATIVE_10>(
+                &mut token_bridge_state,
+                msg,
+                test_scenario::ctx(scenario)
+            );
+
+        // Clean up.
+        complete_transfer_with_payload::burn(receipt);
+
+        abort 42
+    }
+}

+ 643 - 0
target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/create_wrapped.move

@@ -0,0 +1,643 @@
+// SPDX-License-Identifier: Apache 2
+
+/// This module implements methods that create a specific coin type reflecting a
+/// wrapped (foreign) asset, whose metadata is encoded in a VAA sent from
+/// another network.
+///
+/// Wrapped assets are created in two steps.
+///   1. `prepare_registration`: This method creates a new `TreasuryCap` for a
+///      given coin type and wraps an encoded asset metadata VAA. We require a
+///      one-time witness (OTW) to throw an explicit error (even though it is
+///      redundant with what `create_currency` requires). This coin will
+///      be published using this method, meaning the `init` method in that
+///      untrusted package will have the asset's decimals hard-coded for its
+///      coin metadata. A `WrappedAssetSetup` object is transferred to the
+///      transaction sender.
+///   2. `complete_registration`: This method destroys the `WrappedAssetSetup`
+///      object by unpacking its `TreasuryCap`, which will be warehoused in the
+///      `TokenRegistry`. The shared coin metadata object will be updated to
+///      reflect the contents of the encoded asset metadata payload.
+///
+/// Wrapped asset metadata can also be updated with a new asset metadata VAA.
+/// By calling `update_attestation`, Token Bridge verifies that the specific
+/// coin type is registered and agrees with the encoded asset metadata's
+/// canonical token info. `ForeignInfo` and the coin's metadata will be updated
+/// based on the encoded asset metadata payload.
+///
+/// See `state` and `wrapped_asset` modules for more details.
+///
+/// References:
+/// https://examples.sui.io/basics/one-time-witness.html
+module token_bridge::create_wrapped {
+    use std::ascii::{Self};
+    use std::option::{Self};
+    use std::type_name::{Self};
+    use sui::coin::{Self, TreasuryCap, CoinMetadata};
+    use sui::object::{Self, UID};
+    use sui::package::{UpgradeCap};
+    use sui::transfer::{Self};
+    use sui::tx_context::{TxContext};
+
+    use token_bridge::asset_meta::{Self};
+    use token_bridge::normalized_amount::{max_decimals};
+    use token_bridge::state::{Self, State};
+    use token_bridge::token_registry::{Self};
+    use token_bridge::vaa::{Self, TokenBridgeMessage};
+    use token_bridge::wrapped_asset::{Self};
+
+    #[test_only]
+    use token_bridge::version_control::{Self, V__0_2_0 as V__CURRENT};
+
+    /// Failed one-time witness verification.
+    const E_BAD_WITNESS: u64 = 0;
+    /// Coin witness does not equal "COIN".
+    const E_INVALID_COIN_MODULE_NAME: u64 = 1;
+    /// Decimals value exceeds `MAX_DECIMALS` from `normalized_amount`.
+    const E_DECIMALS_EXCEED_WRAPPED_MAX: u64 = 2;
+
+    /// A.K.A. "coin".
+    const COIN_MODULE_NAME: vector<u8> = b"coin";
+
+    /// Container holding new coin type's `TreasuryCap` and encoded asset metadata
+    /// VAA, which are required to complete this asset's registration.
+    struct WrappedAssetSetup<phantom CoinType, phantom Version> has key, store {
+        id: UID,
+        treasury_cap: TreasuryCap<CoinType>
+    }
+
+    /// This method is executed within the `init` method of an untrusted module,
+    /// which defines a one-time witness (OTW) type (`CoinType`). OTW is
+    /// required to ensure that only one `TreasuryCap` exists for `CoinType`. This
+    /// is similar to how a `TreasuryCap` is created in `coin::create_currency`.
+    ///
+    /// Because this method is stateless (i.e. no dependency on Token Bridge's
+    /// `State` object), the contract defers VAA verification to
+    /// `complete_registration` after this method has been executed.
+    public fun prepare_registration<CoinType: drop, Version>(
+        witness: CoinType,
+        decimals: u8,
+        ctx: &mut TxContext
+    ): WrappedAssetSetup<CoinType, Version> {
+        let setup = prepare_registration_internal(witness, decimals, ctx);
+
+        // Also make sure that this witness module name is literally "coin".
+        let module_name = type_name::get_module(&type_name::get<CoinType>());
+        assert!(
+            ascii::into_bytes(module_name) == COIN_MODULE_NAME,
+            E_INVALID_COIN_MODULE_NAME
+        );
+
+        setup
+    }
+
+    #[allow(lint(share_owned))]
+    /// This function performs the bulk of `prepare_registration`, except
+    /// checking the module name. This separation is useful for testing.
+    fun prepare_registration_internal<CoinType: drop, Version>(
+        witness: CoinType,
+        decimals: u8,
+        ctx: &mut TxContext
+    ): WrappedAssetSetup<CoinType, Version> {
+        // Make sure there's only one instance of the type `CoinType`. This
+        // resembles the same check for `coin::create_currency`.
+        // Technically this check is redundant as it's performed by
+        // `coin::create_currency` below, but it doesn't hurt.
+        assert!(sui::types::is_one_time_witness(&witness), E_BAD_WITNESS);
+
+        // Ensure that the decimals passed into this method do not exceed max
+        // decimals (see `normalized_amount` module).
+        assert!(decimals <= max_decimals(), E_DECIMALS_EXCEED_WRAPPED_MAX);
+
+        // We initialise the currency with empty metadata. Later on, in the
+        // `complete_registration` call, when `CoinType` gets associated with a
+        // VAA, we update these fields.
+        let no_symbol = b"";
+        let no_name = b"";
+        let no_description = b"";
+        let no_icon_url = option::none();
+
+        let (treasury_cap, coin_meta) =
+            coin::create_currency(
+                witness,
+                decimals,
+                no_symbol,
+                no_name,
+                no_description,
+                no_icon_url,
+                ctx
+            );
+
+        // The CoinMetadata is turned into a shared object so that other
+        // functions (and wallets) can easily grab references to it. This is
+        // safe to do, as the metadata setters require a `TreasuryCap` for the
+        // coin too, which is held by the token bridge.
+        transfer::public_share_object(coin_meta);
+
+        // Create `WrappedAssetSetup` object and transfer to transaction sender.
+        // The owner of this object will call `complete_registration` to destroy
+        // it.
+        WrappedAssetSetup {
+            id: object::new(ctx),
+            treasury_cap
+        }
+    }
+
+    /// After executing `prepare_registration`, owner of `WrappedAssetSetup`
+    /// executes this method to complete this wrapped asset's registration.
+    ///
+    /// This method destroys `WrappedAssetSetup`, unpacking the `TreasuryCap` and
+    /// encoded asset metadata VAA. The deserialized asset metadata VAA is used
+    /// to update the associated `CoinMetadata`.
+    public fun complete_registration<CoinType: drop, Version>(
+        token_bridge_state: &mut State,
+        coin_meta: &mut CoinMetadata<CoinType>,
+        setup: WrappedAssetSetup<CoinType, Version>,
+        coin_upgrade_cap: UpgradeCap,
+        msg: TokenBridgeMessage
+    ) {
+        // This capability ensures that the current build version is used. This
+        // call performs an additional check of whether `WrappedAssetSetup` was
+        // created using the current package.
+        let latest_only =
+            state::assert_latest_only_specified<Version>(token_bridge_state);
+
+        let WrappedAssetSetup {
+            id,
+            treasury_cap
+        } = setup;
+
+        // Finally destroy the object.
+        object::delete(id);
+
+        // Deserialize to `AssetMeta`.
+        let token_meta = asset_meta::deserialize(vaa::take_payload(msg));
+
+        // `register_wrapped_asset` uses `token_registry::add_new_wrapped`,
+        // which will check whether the asset has already been registered and if
+        // the token chain ID is not Sui's.
+        //
+        // If both of these conditions are met, `register_wrapped_asset` will
+        // succeed and the new wrapped coin will be registered.
+        token_registry::add_new_wrapped(
+            state::borrow_mut_token_registry(&latest_only, token_bridge_state),
+            token_meta,
+            coin_meta,
+            treasury_cap,
+            coin_upgrade_cap
+        );
+    }
+
+    /// For registered wrapped assets, we can update `ForeignInfo` for a
+    /// given `CoinType` with a new asset meta VAA emitted from another network.
+    public fun update_attestation<CoinType>(
+        token_bridge_state: &mut State,
+        coin_meta: &mut CoinMetadata<CoinType>,
+        msg: TokenBridgeMessage
+    ) {
+        // This capability ensures that the current build version is used.
+        let latest_only = state::assert_latest_only(token_bridge_state);
+
+        // Deserialize to `AssetMeta`.
+        let token_meta = asset_meta::deserialize(vaa::take_payload(msg));
+
+        // This asset must exist in the registry.
+        let registry =
+            state::borrow_mut_token_registry(&latest_only, token_bridge_state);
+        token_registry::assert_has<CoinType>(registry);
+
+        // Now update wrapped.
+        wrapped_asset::update_metadata(
+            token_registry::borrow_mut_wrapped<CoinType>(registry),
+            coin_meta,
+            token_meta
+        );
+    }
+
+    public fun incomplete_metadata<CoinType>(
+        coin_meta: &CoinMetadata<CoinType>
+    ): bool {
+        use std::string::{bytes};
+        use std::vector::{is_empty};
+
+        (
+            is_empty(ascii::as_bytes(&coin::get_symbol(coin_meta))) &&
+            is_empty(bytes(&coin::get_name(coin_meta))) &&
+            is_empty(bytes(&coin::get_description(coin_meta))) &&
+            std::option::is_none(&coin::get_icon_url(coin_meta))
+        )
+    }
+
+    #[test_only]
+    public fun new_setup_test_only<CoinType: drop, Version: drop>(
+        _version: Version,
+        witness: CoinType,
+        decimals: u8,
+        ctx: &mut TxContext
+    ): (WrappedAssetSetup<CoinType, Version>, UpgradeCap) {
+        let setup =
+            prepare_registration_internal(
+                witness,
+                decimals,
+                ctx
+            );
+
+        let upgrade_cap =
+            sui::package::test_publish(
+                object::id_from_address(@token_bridge),
+                ctx
+            );
+
+        (setup, upgrade_cap)
+    }
+
+    #[test_only]
+    public fun new_setup_current<CoinType: drop>(
+        witness: CoinType,
+        decimals: u8,
+        ctx: &mut TxContext
+    ): (WrappedAssetSetup<CoinType, V__CURRENT>, UpgradeCap) {
+        new_setup_test_only(
+            version_control::current_version_test_only(),
+            witness,
+            decimals,
+            ctx
+        )
+    }
+
+    #[test_only]
+    public fun take_treasury_cap<CoinType>(
+        setup: WrappedAssetSetup<CoinType, V__CURRENT>
+    ): TreasuryCap<CoinType> {
+        let WrappedAssetSetup {
+            id,
+            treasury_cap
+        } = setup;
+        object::delete(id);
+
+        treasury_cap
+    }
+}
+
+#[test_only]
+module token_bridge::create_wrapped_tests {
+    use sui::coin::{Self};
+    use sui::test_scenario::{Self};
+    use sui::test_utils::{Self};
+    use sui::tx_context::{Self};
+    use wormhole::wormhole_scenario::{parse_and_verify_vaa};
+
+    use token_bridge::asset_meta::{Self};
+    use token_bridge::coin_wrapped_12::{Self};
+    use token_bridge::coin_wrapped_7::{Self};
+    use token_bridge::create_wrapped::{Self};
+    use token_bridge::state::{Self};
+    use token_bridge::string_utils::{Self};
+    use token_bridge::token_bridge_scenario::{
+        register_dummy_emitter,
+        return_state,
+        set_up_wormhole_and_token_bridge,
+        take_state,
+        two_people
+    };
+    use token_bridge::token_registry::{Self};
+    use token_bridge::vaa::{Self};
+    use token_bridge::version_control::{V__0_2_0 as V__CURRENT};
+    use token_bridge::wrapped_asset::{Self};
+
+    struct NOT_A_WITNESS has drop {}
+
+    struct CREATE_WRAPPED_TESTS has drop {}
+
+    #[test]
+    #[expected_failure(abort_code = create_wrapped::E_BAD_WITNESS)]
+    fun test_cannot_prepare_registration_bad_witness() {
+        let ctx = &mut tx_context::dummy();
+
+        // You shall not pass!
+        let wrapped_asset_setup =
+            create_wrapped::prepare_registration<NOT_A_WITNESS, V__CURRENT>(
+                NOT_A_WITNESS {},
+                3,
+                ctx
+            );
+
+        // Clean up.
+        test_utils::destroy(wrapped_asset_setup);
+
+        abort 42
+    }
+
+    #[test]
+    #[expected_failure(abort_code = create_wrapped::E_INVALID_COIN_MODULE_NAME)]
+    fun test_cannot_prepare_registration_invalid_coin_module_name() {
+        let ctx = &mut tx_context::dummy();
+
+        // You shall not pass!
+        let wrapped_asset_setup =
+            create_wrapped::prepare_registration<
+                CREATE_WRAPPED_TESTS,
+                V__CURRENT
+            >(
+                CREATE_WRAPPED_TESTS {},
+                3,
+                ctx
+            );
+
+        // Clean up.
+        test_utils::destroy(wrapped_asset_setup);
+
+        abort 42
+    }
+
+    #[test]
+    fun test_complete_and_update_attestation() {
+        let (caller, coin_deployer) = two_people();
+        let my_scenario = test_scenario::begin(caller);
+        let scenario = &mut my_scenario;
+
+        // Set up contracts.
+        let wormhole_fee = 350;
+        set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
+
+        // Register foreign emitter on chain ID == 2.
+        let expected_source_chain = 2;
+        register_dummy_emitter(scenario, expected_source_chain);
+
+        // Ignore effects. Make sure `coin_deployer` receives
+        // `WrappedAssetSetup`.
+        test_scenario::next_tx(scenario, coin_deployer);
+
+        // Publish coin.
+        let (
+            wrapped_asset_setup,
+            upgrade_cap
+        ) =
+            create_wrapped::new_setup_current(
+                CREATE_WRAPPED_TESTS {},
+                8,
+                test_scenario::ctx(scenario)
+            );
+
+        let token_bridge_state = take_state(scenario);
+
+        let verified_vaa =
+            parse_and_verify_vaa(scenario, coin_wrapped_12::encoded_vaa());
+        let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa);
+
+        let coin_meta = test_scenario::take_shared(scenario);
+
+        create_wrapped::complete_registration(
+            &mut token_bridge_state,
+            &mut coin_meta,
+            wrapped_asset_setup,
+            upgrade_cap,
+            msg
+        );
+
+        let (
+            token_address,
+            token_chain,
+            native_decimals,
+            symbol,
+            name
+        ) = asset_meta::unpack_test_only(coin_wrapped_12::token_meta());
+
+        // Check registry.
+        {
+            let registry = state::borrow_token_registry(&token_bridge_state);
+            let verified =
+                token_registry::verified_asset<CREATE_WRAPPED_TESTS>(registry);
+            assert!(token_registry::is_wrapped(&verified), 0);
+
+            let asset =
+                token_registry::borrow_wrapped<CREATE_WRAPPED_TESTS>(registry);
+            assert!(wrapped_asset::total_supply(asset) == 0, 0);
+
+            // Decimals are capped for this wrapped asset.
+            assert!(coin::get_decimals(&coin_meta) == 8, 0);
+
+            // Check metadata against asset metadata.
+            let info = wrapped_asset::info(asset);
+            assert!(wrapped_asset::token_chain(info) == token_chain, 0);
+            assert!(wrapped_asset::token_address(info) == token_address, 0);
+            assert!(
+                wrapped_asset::native_decimals(info) == native_decimals,
+                0
+            );
+            assert!(coin::get_symbol(&coin_meta) == string_utils::to_ascii(&symbol), 0);
+            assert!(coin::get_name(&coin_meta) == name, 0);
+        };
+
+
+        // Now update metadata.
+        let verified_vaa =
+            parse_and_verify_vaa(
+                scenario,
+                coin_wrapped_12::encoded_updated_vaa()
+            );
+        let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa);
+        create_wrapped::update_attestation<CREATE_WRAPPED_TESTS>(
+            &mut token_bridge_state,
+            &mut coin_meta,
+            msg
+        );
+
+        // Check updated name and symbol.
+        let (
+            _,
+            _,
+            _,
+            new_symbol,
+            new_name
+        ) = asset_meta::unpack_test_only(coin_wrapped_12::updated_token_meta());
+
+        assert!(symbol != new_symbol, 0);
+
+        assert!(coin::get_symbol(&coin_meta) == string_utils::to_ascii(&new_symbol), 0);
+
+        assert!(name != new_name, 0);
+        assert!(coin::get_name(&coin_meta) == new_name, 0);
+
+        test_scenario::return_shared(coin_meta);
+
+        // Clean up.
+        return_state(token_bridge_state);
+
+        // Done.
+        test_scenario::end(my_scenario);
+    }
+
+    #[test]
+    #[expected_failure(abort_code = wrapped_asset::E_ASSET_META_MISMATCH)]
+    fun test_cannot_update_attestation_wrong_canonical_info() {
+        let (caller, coin_deployer) = two_people();
+        let my_scenario = test_scenario::begin(caller);
+        let scenario = &mut my_scenario;
+
+        // Set up contracts.
+        let wormhole_fee = 350;
+        set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
+
+        // Register foreign emitter on chain ID == 2.
+        let expected_source_chain = 2;
+        register_dummy_emitter(scenario, expected_source_chain);
+
+        // Ignore effects. Make sure `coin_deployer` receives
+        // `WrappedAssetSetup`.
+        test_scenario::next_tx(scenario, coin_deployer);
+
+        // Publish coin.
+        let (
+            wrapped_asset_setup,
+            upgrade_cap
+        ) =
+            create_wrapped::new_setup_current(
+                CREATE_WRAPPED_TESTS {},
+                8,
+                test_scenario::ctx(scenario)
+            );
+
+        let token_bridge_state = take_state(scenario);
+
+        let verified_vaa =
+            parse_and_verify_vaa(scenario, coin_wrapped_12::encoded_vaa());
+        let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa);
+
+        let coin_meta = test_scenario::take_shared(scenario);
+
+        create_wrapped::complete_registration(
+            &mut token_bridge_state,
+            &mut coin_meta,
+            wrapped_asset_setup,
+            upgrade_cap,
+            msg
+        );
+        // This VAA is for COIN_WRAPPED_7 metadata, which disagrees with
+        // COIN_WRAPPED_12.
+        let invalid_asset_meta_vaa = coin_wrapped_7::encoded_vaa();
+
+        let verified_vaa =
+            parse_and_verify_vaa(scenario, invalid_asset_meta_vaa);
+        let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa);
+        // You shall not pass!
+        create_wrapped::update_attestation<CREATE_WRAPPED_TESTS>(
+            &mut token_bridge_state,
+            &mut coin_meta,
+            msg
+        );
+
+        abort 42
+    }
+
+    #[test]
+    #[expected_failure(abort_code = state::E_VERSION_MISMATCH)]
+    fun test_cannot_complete_registration_version_mismatch() {
+        let (caller, coin_deployer) = two_people();
+        let my_scenario = test_scenario::begin(caller);
+        let scenario = &mut my_scenario;
+
+        // Set up contracts.
+        let wormhole_fee = 350;
+        set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
+
+        // Register foreign emitter on chain ID == 2.
+        let expected_source_chain = 2;
+        register_dummy_emitter(scenario, expected_source_chain);
+
+        // Ignore effects. Make sure `coin_deployer` receives
+        // `WrappedAssetSetup`.
+        test_scenario::next_tx(scenario, coin_deployer);
+
+        // Publish coin.
+        let (
+            wrapped_asset_setup,
+            upgrade_cap
+        ) =
+            create_wrapped::new_setup_test_only(
+                token_bridge::version_control::dummy(),
+                CREATE_WRAPPED_TESTS {},
+                8,
+                test_scenario::ctx(scenario)
+            );
+
+        let token_bridge_state = take_state(scenario);
+
+        let verified_vaa =
+            parse_and_verify_vaa(scenario, coin_wrapped_12::encoded_vaa());
+        let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa);
+
+        let coin_meta = test_scenario::take_shared(scenario);
+
+        create_wrapped::complete_registration(
+            &mut token_bridge_state,
+            &mut coin_meta,
+            wrapped_asset_setup,
+            upgrade_cap,
+            msg
+        );
+
+        abort 42
+    }
+
+    #[test]
+    #[expected_failure(abort_code = wormhole::package_utils::E_NOT_CURRENT_VERSION)]
+    fun test_cannot_complete_registration_outdated_version() {
+        let (caller, coin_deployer) = two_people();
+        let my_scenario = test_scenario::begin(caller);
+        let scenario = &mut my_scenario;
+
+        // Set up contracts.
+        let wormhole_fee = 350;
+        set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
+
+        // Register foreign emitter on chain ID == 2.
+        let expected_source_chain = 2;
+        register_dummy_emitter(scenario, expected_source_chain);
+
+        // Ignore effects. Make sure `coin_deployer` receives
+        // `WrappedAssetSetup`.
+        test_scenario::next_tx(scenario, coin_deployer);
+
+        // Publish coin.
+        let (
+            wrapped_asset_setup,
+            upgrade_cap
+        ) =
+            create_wrapped::new_setup_current(
+                CREATE_WRAPPED_TESTS {},
+                8,
+                test_scenario::ctx(scenario)
+            );
+
+        let token_bridge_state = take_state(scenario);
+
+        let verified_vaa =
+            parse_and_verify_vaa(scenario, coin_wrapped_12::encoded_vaa());
+        let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa);
+
+        let coin_meta = test_scenario::take_shared(scenario);
+
+        // Conveniently roll version back.
+        state::reverse_migrate_version(&mut token_bridge_state);
+
+        // Simulate executing with an outdated build by upticking the minimum
+        // required version for `publish_message` to something greater than
+        // this build.
+        state::migrate_version_test_only(
+            &mut token_bridge_state,
+            token_bridge::version_control::previous_version_test_only(),
+            token_bridge::version_control::next_version()
+        );
+
+        // You shall not pass!
+        create_wrapped::complete_registration(
+            &mut token_bridge_state,
+            &mut coin_meta,
+            wrapped_asset_setup,
+            upgrade_cap,
+            msg
+        );
+
+        abort 42
+    }
+}

+ 167 - 0
target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/datatypes/normalized_amount.move

@@ -0,0 +1,167 @@
+// SPDX-License-Identifier: Apache 2
+
+/// This module implements a container that stores the token transfer amount
+/// encoded in a Token Bridge message. These amounts are capped at 8 decimals.
+/// This means that any amount of a coin whose metadata defines its decimals
+/// as some value greater than 8, the encoded amount will be normalized to
+/// eight decimals (which will lead to some residual amount after the transfer).
+/// For inbound transfers, this amount will be denormalized (scaled by the same
+/// decimal difference).
+module token_bridge::normalized_amount {
+    use sui::math::{Self};
+    use wormhole::bytes32::{Self};
+    use wormhole::cursor::{Cursor};
+
+    /// The amounts in the token bridge payload are truncated to 8 decimals
+    /// in each of the contracts when sending tokens out, so there's no
+    /// precision beyond 10^-8. We could preserve the original number of
+    /// decimals when creating wrapped assets, and "untruncate" the amounts
+    /// on the way out by scaling back appropriately. This is what most
+    /// other chains do, but untruncating from 8 decimals to 18 decimals
+    /// loses log2(10^10) ~ 33 bits of precision, which we cannot afford on
+    /// Aptos (and Solana), as the coin type only has 64bits to begin with.
+    /// Contrast with Ethereum, where amounts are 256 bits.
+    /// So we cap the maximum decimals at 8 when creating a wrapped token.
+    const MAX_DECIMALS: u8 = 8;
+
+    /// Container holding the value decoded from a Token Bridge transfer.
+    struct NormalizedAmount has store, copy, drop {
+        value: u64
+    }
+
+    public fun max_decimals(): u8 {
+        MAX_DECIMALS
+    }
+
+    /// Utility function to cap decimal amount to 8.
+    public fun cap_decimals(decimals: u8): u8 {
+        if (decimals > MAX_DECIMALS) {
+            MAX_DECIMALS
+        } else {
+            decimals
+        }
+    }
+
+    /// Create new `NormalizedAmount` of zero.
+    public fun default(): NormalizedAmount {
+        new(0)
+    }
+
+    /// Retrieve underlying value.
+    public fun value(self: &NormalizedAmount): u64 {
+        self.value
+    }
+
+    /// Retrieve underlying value as `u256`.
+    public fun to_u256(norm: NormalizedAmount): u256 {
+        (take_value(norm) as u256)
+    }
+
+    /// Create new `NormalizedAmount` using raw amount and specified decimals.
+    public fun from_raw(amount: u64, decimals: u8): NormalizedAmount {
+        if (amount == 0) {
+            default()
+        } else if (decimals > MAX_DECIMALS) {
+            new(amount / math::pow(10, decimals - MAX_DECIMALS))
+        } else {
+            new(amount)
+        }
+    }
+
+    /// Denormalize `NormalizedAmount` using specified decimals.
+    public fun to_raw(norm: NormalizedAmount, decimals: u8): u64 {
+        let value = take_value(norm);
+
+        if (value > 0 && decimals > MAX_DECIMALS) {
+            value * math::pow(10, decimals - MAX_DECIMALS)
+        } else {
+            value
+        }
+    }
+
+    /// Transform `NormalizedAmount` to serialized (big-endian) u256.
+    public fun to_bytes(norm: NormalizedAmount): vector<u8> {
+        bytes32::to_bytes(bytes32::from_u256_be(to_u256(norm)))
+    }
+
+    /// Read 32 bytes from `Cursor` and deserialize to u64, ensuring no
+    /// overflow.
+    public fun take_bytes(cur: &mut Cursor<u8>): NormalizedAmount {
+        // Amounts are encoded with 32 bytes.
+        new(bytes32::to_u64_be(bytes32::take_bytes(cur)))
+    }
+
+    fun new(value: u64): NormalizedAmount {
+        NormalizedAmount {
+            value
+        }
+    }
+
+    fun take_value(norm: NormalizedAmount): u64 {
+        let NormalizedAmount { value } = norm;
+        value
+    }
+}
+
+#[test_only]
+module token_bridge::normalized_amount_test {
+    use wormhole::bytes::{Self};
+    use wormhole::cursor::{Self};
+
+    use token_bridge::normalized_amount::{Self};
+
+    #[test]
+    fun test_from_and_to_raw() {
+        // Use decimals > 8 to check truncation.
+        let decimals = 9;
+        let raw_amount = 12345678910111;
+        let normalized = normalized_amount::from_raw(raw_amount, decimals);
+        let denormalized = normalized_amount::to_raw(normalized, decimals);
+        assert!(denormalized == 10 * (raw_amount / 10), 0);
+
+        // Use decimals <= 8 to check raw amount recovery.
+        let decimals = 5;
+        let normalized = normalized_amount::from_raw(raw_amount, decimals);
+        let denormalized = normalized_amount::to_raw(normalized, decimals);
+        assert!(denormalized == raw_amount, 0);
+    }
+
+    #[test]
+    fun test_take_bytes() {
+        let cur =
+            cursor::new(
+                x"000000000000000000000000000000000000000000000000ffffffffffffffff"
+            );
+
+        let norm = normalized_amount::take_bytes(&mut cur);
+        assert!(
+            normalized_amount::value(&norm) == ((1u256 << 64) - 1 as u64),
+            0
+        );
+
+        // Clean up.
+        cursor::destroy_empty(cur);
+    }
+
+    #[test]
+    #[expected_failure(abort_code = wormhole::bytes32::E_U64_OVERFLOW)]
+    fun test_cannot_take_bytes_overflow() {
+        let encoded_overflow =
+            x"0000000000000000000000000000000000000000000000010000000000000000";
+
+        let amount = {
+            let cur = cursor::new(encoded_overflow);
+            let value = bytes::take_u256_be(&mut cur);
+            cursor::destroy_empty(cur);
+            value
+        };
+        assert!(amount == (1 << 64), 0);
+
+        let cur = cursor::new(encoded_overflow);
+
+        // You shall not pass!
+        normalized_amount::take_bytes(&mut cur);
+
+        abort 42
+    }
+}

+ 297 - 0
target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/governance/register_chain.move

@@ -0,0 +1,297 @@
+// SPDX-License-Identifier: Apache 2
+
+/// This module implements handling a governance VAA to enact registering a
+/// foreign Token Bridge for a particular chain ID.
+module token_bridge::register_chain {
+    use sui::table::{Self};
+    use wormhole::bytes::{Self};
+    use wormhole::cursor::{Self};
+    use wormhole::external_address::{Self, ExternalAddress};
+    use wormhole::governance_message::{Self, DecreeTicket, DecreeReceipt};
+
+    use token_bridge::state::{Self, State, LatestOnly};
+
+    /// Cannot register chain ID == 0.
+    const E_INVALID_EMITTER_CHAIN: u64 = 0;
+    /// Emitter already exists for a given chain ID.
+    const E_EMITTER_ALREADY_REGISTERED: u64 = 1;
+
+    /// Specific governance payload ID (action) for registering foreign Token
+    /// Bridge contract address.
+    const ACTION_REGISTER_CHAIN: u8 = 1;
+
+    struct GovernanceWitness has drop {}
+
+    struct RegisterChain {
+        chain: u16,
+        contract_address: ExternalAddress,
+    }
+
+    public fun authorize_governance(
+        token_bridge_state: &State
+    ): DecreeTicket<GovernanceWitness> {
+        governance_message::authorize_verify_global(
+            GovernanceWitness {},
+            state::governance_chain(token_bridge_state),
+            state::governance_contract(token_bridge_state),
+            state::governance_module(),
+            ACTION_REGISTER_CHAIN
+        )
+    }
+
+    public fun register_chain(
+        token_bridge_state: &mut State,
+        receipt: DecreeReceipt<GovernanceWitness>
+    ): (
+        u16,
+        ExternalAddress
+    ) {
+        // This capability ensures that the current build version is used.
+        let latest_only = state::assert_latest_only(token_bridge_state);
+
+        let payload =
+            governance_message::take_payload(
+                state::borrow_mut_consumed_vaas(
+                    &latest_only,
+                    token_bridge_state
+                ),
+                receipt
+            );
+
+        handle_register_chain(&latest_only, token_bridge_state, payload)
+    }
+
+    fun handle_register_chain(
+        latest_only: &LatestOnly,
+        token_bridge_state: &mut State,
+        governance_payload: vector<u8>
+    ): (
+        u16,
+        ExternalAddress
+    ) {
+        // Deserialize the payload as amount to change the Wormhole fee.
+        let RegisterChain {
+            chain,
+            contract_address
+        } = deserialize(governance_payload);
+
+        register_new_emitter(
+            latest_only,
+            token_bridge_state,
+            chain,
+            contract_address
+        );
+
+        (chain, contract_address)
+    }
+
+    fun deserialize(payload: vector<u8>): RegisterChain {
+        let cur = cursor::new(payload);
+
+        // This amount cannot be greater than max u64.
+        let chain = bytes::take_u16_be(&mut cur);
+        let contract_address = external_address::take_bytes(&mut cur);
+
+        cursor::destroy_empty(cur);
+
+        RegisterChain { chain, contract_address}
+    }
+
+    /// Add a new Token Bridge emitter to the registry. This method will abort
+    /// if an emitter is already registered for a particular chain ID.
+    ///
+    /// See `register_chain` module for more info.
+    fun register_new_emitter(
+        latest_only: &LatestOnly,
+        token_bridge_state: &mut State,
+        chain: u16,
+        contract_address: ExternalAddress
+    ) {
+        assert!(chain != 0, E_INVALID_EMITTER_CHAIN);
+
+        let registry =
+            state::borrow_mut_emitter_registry(latest_only, token_bridge_state);
+        assert!(
+            !table::contains(registry, chain),
+            E_EMITTER_ALREADY_REGISTERED
+        );
+        table::add(registry, chain, contract_address);
+    }
+
+    #[test_only]
+    public fun register_new_emitter_test_only(
+        token_bridge_state: &mut State,
+        chain: u16,
+        contract_address: ExternalAddress
+    ) {
+        // This capability ensures that the current build version is used.
+        let latest_only = state::assert_latest_only(token_bridge_state);
+
+        register_new_emitter(
+            &latest_only,
+            token_bridge_state,
+            chain,
+            contract_address
+        );
+    }
+
+    #[test_only]
+    public fun action(): u8 {
+        ACTION_REGISTER_CHAIN
+    }
+}
+
+#[test_only]
+module token_bridge::register_chain_tests {
+    use sui::table::{Self};
+    use sui::test_scenario::{Self};
+    use wormhole::bytes::{Self};
+    use wormhole::cursor::{Self};
+    use wormhole::external_address::{Self};
+    use wormhole::governance_message::{Self};
+    use wormhole::wormhole_scenario::{
+        parse_and_verify_vaa,
+        verify_governance_vaa
+    };
+
+    use token_bridge::register_chain::{Self};
+    use token_bridge::state::{Self};
+    use token_bridge::token_bridge_scenario::{
+        person,
+        return_state,
+        set_up_wormhole_and_token_bridge,
+        take_state
+    };
+
+    const VAA_REGISTER_CHAIN_1: vector<u8> =
+        x"01000000000100dd8cf046ad6dd17b2b5130d236b3545350899ac33b5c9e93e4d8c3e0da718a351c3f76cb9ddb15a0f0d7db7b1dded2b5e79c2f6e76dde6d8ed4bcb9cb461eb480100bc614e0000000000010000000000000000000000000000000000000000000000000000000000000004000000000000000101000000000000000000000000000000000000000000546f6b656e4272696467650100000002000000000000000000000000deadbeefdeadbeefdeadbeefdeadbeefdeadbeef";
+    const VAA_REGISTER_SAME_CHAIN: vector<u8> =
+        x"01000000000100847ca782db7616135de4a835ed5b12ba7946bbd39f70ecd9912ec55bdc9cb6c6215c98d6ad5c8d7253c2bb0fb0f8df0dc6591408c366cf0c09e58abcfb8c0abe0000bc614e0000000000010000000000000000000000000000000000000000000000000000000000000004000000000000000101000000000000000000000000000000000000000000546f6b656e427269646765010000000200000000000000000000000000000000000000000000000000000000deafbeef";
+
+    #[test]
+    fun test_register_chain() {
+        // Testing this method.
+        use token_bridge::register_chain::{register_chain};
+
+        // Set up.
+        let caller = person();
+        let my_scenario = test_scenario::begin(caller);
+        let scenario = &mut my_scenario;
+
+        // Initialize Wormhole and Token Bridge.
+        let wormhole_fee = 350;
+        set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
+
+        // Prepare test to execute `set_fee`.
+        test_scenario::next_tx(scenario, caller);
+
+        let token_bridge_state = take_state(scenario);
+
+        // Check that the emitter is not registered.
+        let expected_chain = 2;
+        {
+            let registry = state::borrow_emitter_registry(&token_bridge_state);
+            assert!(!table::contains(registry, expected_chain), 0);
+        };
+
+        let verified_vaa = parse_and_verify_vaa(scenario, VAA_REGISTER_CHAIN_1);
+        let ticket = register_chain::authorize_governance(&token_bridge_state);
+        let receipt =
+            verify_governance_vaa(scenario, verified_vaa, ticket);
+        let (
+            chain,
+            contract_address
+        ) = register_chain(&mut token_bridge_state, receipt);
+        assert!(chain == expected_chain, 0);
+
+        let expected_contract =
+            external_address::from_address(
+                @0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef
+            );
+        assert!(contract_address == expected_contract, 0);
+        {
+            let registry = state::borrow_emitter_registry(&token_bridge_state);
+            assert!(*table::borrow(registry, expected_chain) == expected_contract, 0);
+        };
+
+        // Clean up.
+        return_state(token_bridge_state);
+
+        // Done.
+        test_scenario::end(my_scenario);
+    }
+
+    #[test]
+    #[expected_failure(abort_code = register_chain::E_EMITTER_ALREADY_REGISTERED)]
+    fun test_cannot_register_chain_already_registered() {
+        // Testing this method.
+        use token_bridge::register_chain::{register_chain};
+
+        // Set up.
+        let caller = person();
+        let my_scenario = test_scenario::begin(caller);
+        let scenario = &mut my_scenario;
+
+        // Initialize Wormhole and Token Bridge.
+        let wormhole_fee = 350;
+        set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
+
+        // Prepare test to execute `set_fee`.
+        test_scenario::next_tx(scenario, caller);
+
+        let token_bridge_state = take_state(scenario);
+
+        let verified_vaa = parse_and_verify_vaa(scenario, VAA_REGISTER_CHAIN_1);
+        let ticket = register_chain::authorize_governance(&token_bridge_state);
+        let receipt =
+            verify_governance_vaa(scenario, verified_vaa, ticket);
+        let (
+            chain,
+            _
+        ) = register_chain(&mut token_bridge_state, receipt);
+
+        // Check registry.
+        let expected_contract =
+            *table::borrow(
+                state::borrow_emitter_registry(&token_bridge_state),
+                chain
+            );
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, caller);
+
+        let verified_vaa =
+            parse_and_verify_vaa(scenario, VAA_REGISTER_SAME_CHAIN);
+        let payload =
+            governance_message::take_decree(
+                wormhole::vaa::payload(&verified_vaa)
+            );
+        let cur = cursor::new(payload);
+
+        // Show this payload is attempting to register the same chain ID.
+        let another_chain = bytes::take_u16_be(&mut cur);
+        assert!(chain == another_chain, 0);
+
+        let another_contract = external_address::take_bytes(&mut cur);
+        assert!(another_contract != expected_contract, 0);
+
+        // No more payload to read.
+        cursor::destroy_empty(cur);
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, caller);
+
+        let ticket = register_chain::authorize_governance(&token_bridge_state);
+        let receipt =
+            verify_governance_vaa(scenario, verified_vaa, ticket);
+
+        // You shall not pass!
+        register_chain(&mut token_bridge_state, receipt);
+
+        abort 42
+    }
+}
+
+
+
+

+ 125 - 0
target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/governance/upgrade_contract.move

@@ -0,0 +1,125 @@
+// SPDX-License-Identifier: Apache 2
+
+/// This module implements handling a governance VAA to enact upgrading the
+/// Token Bridge contract to a new build. The procedure to upgrade this contract
+/// requires a Programmable Transaction, which includes the following procedure:
+/// 1.  Load new build.
+/// 2.  Authorize upgrade.
+/// 3.  Upgrade.
+/// 4.  Commit upgrade.
+module token_bridge::upgrade_contract {
+    use sui::object::{ID};
+    use sui::package::{UpgradeReceipt, UpgradeTicket};
+    use wormhole::bytes32::{Self, Bytes32};
+    use wormhole::cursor::{Self};
+    use wormhole::governance_message::{Self, DecreeTicket, DecreeReceipt};
+
+    use token_bridge::state::{Self, State};
+
+    friend token_bridge::migrate;
+
+    /// Digest is all zeros.
+    const E_DIGEST_ZERO_BYTES: u64 = 0;
+
+    /// Specific governance payload ID (action) to complete upgrading the
+    /// contract.
+    const ACTION_UPGRADE_CONTRACT: u8 = 2;
+
+    struct GovernanceWitness has drop {}
+
+    // Event reflecting package upgrade.
+    struct ContractUpgraded has drop, copy {
+        old_contract: ID,
+        new_contract: ID
+    }
+
+    struct UpgradeContract {
+        digest: Bytes32
+    }
+
+    public fun authorize_governance(
+        token_bridge_state: &State
+    ): DecreeTicket<GovernanceWitness> {
+        governance_message::authorize_verify_local(
+            GovernanceWitness {},
+            state::governance_chain(token_bridge_state),
+            state::governance_contract(token_bridge_state),
+            state::governance_module(),
+            ACTION_UPGRADE_CONTRACT
+        )
+    }
+
+    /// Redeem governance VAA to issue an `UpgradeTicket` for the upgrade given
+    /// a contract upgrade VAA. This governance message is only relevant for Sui
+    /// because a contract upgrade is only relevant to one particular network
+    /// (in this case Sui), whose build digest is encoded in this message.
+    public fun authorize_upgrade(
+        token_bridge_state: &mut State,
+        receipt: DecreeReceipt<GovernanceWitness>
+    ): UpgradeTicket {
+        // current package checking when consuming VAA hashes. This is because
+        // upgrades are protected by the Sui VM, enforcing the latest package
+        // is the one performing the upgrade.
+        let consumed =
+            state::borrow_mut_consumed_vaas_unchecked(token_bridge_state);
+
+        // And consume.
+        let payload = governance_message::take_payload(consumed, receipt);
+
+        // Proceed with processing new implementation version.
+        handle_upgrade_contract(token_bridge_state, payload)
+    }
+
+    /// Finalize the upgrade that ran to produce the given `receipt`. This
+    /// method invokes `state::commit_upgrade` which interacts with
+    /// `sui::package`.
+    public fun commit_upgrade(
+        self: &mut State,
+        receipt: UpgradeReceipt,
+    ) {
+        let (old_contract, new_contract) = state::commit_upgrade(self, receipt);
+
+        // Emit an event reflecting package ID change.
+        sui::event::emit(ContractUpgraded { old_contract, new_contract });
+    }
+
+    /// Privileged method only to be used by this module and `migrate` module.
+    ///
+    /// During migration, we make sure that the digest equals what we expect by
+    /// passing in the same VAA used to upgrade the package.
+    public(friend) fun take_digest(governance_payload: vector<u8>): Bytes32 {
+        // Deserialize the payload as the build digest.
+        let UpgradeContract { digest } = deserialize(governance_payload);
+
+        digest
+    }
+
+    fun handle_upgrade_contract(
+        wormhole_state: &mut State,
+        payload: vector<u8>
+    ): UpgradeTicket {
+        state::authorize_upgrade(wormhole_state, take_digest(payload))
+    }
+
+    fun deserialize(payload: vector<u8>): UpgradeContract {
+        let cur = cursor::new(payload);
+
+        // This amount cannot be greater than max u64.
+        let digest = bytes32::take_bytes(&mut cur);
+        assert!(bytes32::is_nonzero(&digest), E_DIGEST_ZERO_BYTES);
+
+        cursor::destroy_empty(cur);
+
+        UpgradeContract { digest }
+    }
+
+    #[test_only]
+    public fun action(): u8 {
+        ACTION_UPGRADE_CONTRACT
+    }
+}
+
+#[test_only]
+module token_bridge::upgrade_contract_tests {
+    // TODO
+}

+ 251 - 0
target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/messages/asset_meta.move

@@ -0,0 +1,251 @@
+// SPDX-License-Identifier: Apache 2
+
+/// This module implements serialization and deserialization for asset metadata,
+/// which is a specific Wormhole message payload for Token Bridge.
+module token_bridge::asset_meta {
+    use std::string::{Self, String};
+    use std::vector::{Self};
+    use sui::coin::{Self, CoinMetadata};
+    use wormhole::bytes::{Self};
+    use wormhole::bytes32::{Self};
+    use wormhole::external_address::{Self, ExternalAddress};
+    use wormhole::cursor::{Self};
+    use wormhole::state::{chain_id};
+
+    use token_bridge::native_asset::{Self};
+
+    friend token_bridge::attest_token;
+    friend token_bridge::create_wrapped;
+    friend token_bridge::wrapped_asset;
+
+    /// Message payload is not `AssetMeta`.
+    const E_INVALID_PAYLOAD: u64 = 0;
+
+    /// Message identifier.
+    const PAYLOAD_ID: u8 = 2;
+
+    /// Container that warehouses asset metadata information. This struct is
+    /// used only by `attest_token` and `create_wrapped` modules.
+    struct AssetMeta {
+        /// Address of the token.
+        token_address: ExternalAddress,
+        /// Chain ID of the token.
+        token_chain: u16,
+        /// Number of decimals of the token.
+        native_decimals: u8,
+        /// Symbol of the token (UTF-8).
+        /// TODO(csongor): maybe turn these into String32s?
+        symbol: String,
+        /// Name of the token (UTF-8).
+        name: String,
+    }
+
+
+    public(friend) fun from_metadata<C>(metadata: &CoinMetadata<C>): AssetMeta {
+        AssetMeta {
+            token_address: native_asset::canonical_address(metadata),
+            token_chain: chain_id(),
+            native_decimals: coin::get_decimals(metadata),
+            symbol: string::from_ascii(coin::get_symbol(metadata)),
+            name: coin::get_name(metadata)
+        }
+    }
+
+    #[test_only]
+    public fun from_metadata_test_only<C>(metadata: &CoinMetadata<C>): AssetMeta {
+        from_metadata(metadata)
+    }
+
+    public(friend) fun unpack(
+        meta: AssetMeta
+    ): (
+        ExternalAddress,
+        u16,
+        u8,
+        String,
+        String
+    ) {
+        let AssetMeta {
+            token_address,
+            token_chain,
+            native_decimals,
+            symbol,
+            name
+        } = meta;
+
+        (
+            token_address,
+            token_chain,
+            native_decimals,
+            symbol,
+            name
+        )
+    }
+
+
+    #[test_only]
+    public fun unpack_test_only(
+        meta: AssetMeta
+    ): (
+        ExternalAddress,
+        u16,
+        u8,
+        String,
+        String
+    ) {
+        unpack(meta)
+    }
+
+    public fun token_chain(self: &AssetMeta): u16 {
+        self.token_chain
+    }
+
+    public fun token_address(self: &AssetMeta): ExternalAddress {
+        self.token_address
+    }
+
+    public(friend) fun serialize(meta: AssetMeta): vector<u8> {
+        let (
+            token_address,
+            token_chain,
+            native_decimals,
+            symbol,
+            name
+        ) = unpack(meta);
+
+        let buf = vector::empty<u8>();
+        bytes::push_u8(&mut buf, PAYLOAD_ID);
+        vector::append(&mut buf, external_address::to_bytes(token_address));
+        bytes::push_u16_be(&mut buf, token_chain);
+        bytes::push_u8(&mut buf, native_decimals);
+        vector::append(
+            &mut buf,
+            bytes32::to_bytes(bytes32::from_utf8(symbol))
+        );
+        vector::append(
+            &mut buf,
+            bytes32::to_bytes(bytes32::from_utf8(name))
+        );
+
+        buf
+    }
+
+    #[test_only]
+    public fun serialize_test_only(meta: AssetMeta): vector<u8> {
+        serialize(meta)
+    }
+
+    public(friend) fun deserialize(buf: vector<u8>): AssetMeta {
+        let cur = cursor::new(buf);
+        assert!(bytes::take_u8(&mut cur) == PAYLOAD_ID, E_INVALID_PAYLOAD);
+        let token_address = external_address::take_bytes(&mut cur);
+        let token_chain = bytes::take_u16_be(&mut cur);
+        let native_decimals = bytes::take_u8(&mut cur);
+        let symbol = bytes32::to_utf8(bytes32::take_bytes(&mut cur));
+        let name = bytes32::to_utf8(bytes32::take_bytes(&mut cur));
+        cursor::destroy_empty(cur);
+
+        AssetMeta {
+            token_address,
+            token_chain,
+            native_decimals,
+            symbol,
+            name
+        }
+    }
+
+    #[test_only]
+    public fun deserialize_test_only(buf: vector<u8>): AssetMeta {
+        deserialize(buf)
+    }
+
+    #[test_only]
+    public fun new(
+        token_address: ExternalAddress,
+        token_chain: u16,
+        native_decimals: u8,
+        symbol: String,
+        name: String,
+    ): AssetMeta {
+        AssetMeta {
+            token_address,
+            token_chain,
+            native_decimals,
+            symbol,
+            name
+        }
+    }
+
+    #[test_only]
+    public fun native_decimals(self: &AssetMeta): u8 {
+        self.native_decimals
+    }
+
+    #[test_only]
+    public fun symbol(self: &AssetMeta): String {
+        self.symbol
+    }
+
+    #[test_only]
+    public fun name(self: &AssetMeta): String {
+        self.name
+    }
+
+    #[test_only]
+    public fun destroy(token_meta: AssetMeta) {
+        unpack(token_meta);
+    }
+
+    #[test_only]
+    public fun payload_id(): u8 {
+        PAYLOAD_ID
+    }
+}
+
+#[test_only]
+module token_bridge::asset_meta_tests {
+    use std::string::{Self};
+    use wormhole::external_address::{Self};
+    use wormhole::vaa::{Self};
+
+    use token_bridge::asset_meta::{Self};
+
+    #[test]
+    fun test_serialize_deserialize() {
+        let token_address = external_address::from_address(@0x1122);
+        let symbol = string::utf8(b"a creative symbol");
+        let name = string::utf8(b"a creative name");
+        let asset_meta = asset_meta::new(
+            token_address, //token address
+            3, // token chain
+            4, //native decimals
+            symbol, // symbol
+            name, // name
+        );
+        // Serialize and deserialize TransferWithPayload object.
+        let se = asset_meta::serialize_test_only(asset_meta);
+        let de = asset_meta::deserialize_test_only(se);
+
+        // Test that the object fields are unchanged.
+        assert!(asset_meta::token_chain(&de) == 3, 0);
+        assert!(asset_meta::token_address(&de) == token_address, 0);
+        assert!(asset_meta::native_decimals(&de) == 4, 0);
+        assert!(asset_meta::symbol(&de) ==  symbol, 0);
+        assert!(asset_meta::name(&de) == name, 0);
+
+        // Clean up.
+        asset_meta::destroy(de);
+    }
+
+    #[test]
+    fun test_create_wrapped_12() {
+        use token_bridge::dummy_message::{encoded_asset_meta_vaa_foreign_12};
+
+        let payload =
+            vaa::peel_payload_from_vaa(&encoded_asset_meta_vaa_foreign_12());
+
+        let token_meta = asset_meta::deserialize_test_only(payload);
+        let serialized = asset_meta::serialize_test_only(token_meta);
+        assert!(payload == serialized, 0);
+    }
+}

+ 311 - 0
target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/messages/transfer.move

@@ -0,0 +1,311 @@
+// SPDX-License-Identifier: Apache 2
+
+/// This module implements serialization and deserialization for token transfer
+/// with an optional relayer fee. This message is a specific Wormhole message
+/// payload for Token Bridge.
+///
+/// When this transfer is redeemed, the relayer fee will be subtracted from the
+/// transfer amount. If the transaction sender is the same address of the
+/// recipient, the recipient will collect the full amount.
+///
+/// See `transfer_tokens` and `complete_transfer` modules for more details.
+module token_bridge::transfer {
+    use std::vector::{Self};
+    use wormhole::bytes::{Self};
+    use wormhole::cursor::{Self};
+    use wormhole::external_address::{Self, ExternalAddress};
+
+    use token_bridge::normalized_amount::{Self, NormalizedAmount};
+
+    friend token_bridge::complete_transfer;
+    friend token_bridge::transfer_tokens;
+
+    /// Message payload is not `Transfer`.
+    const E_INVALID_PAYLOAD: u64 = 0;
+
+    /// Message identifier.
+    const PAYLOAD_ID: u8 = 1;
+
+    /// Container that warehouses transfer information. This struct is used only
+    /// by `transfer_tokens` and `complete_transfer` modules.
+    struct Transfer {
+        // Amount being transferred.
+        amount: NormalizedAmount,
+        // Address of the token. Left-zero-padded if shorter than 32 bytes.
+        token_address: ExternalAddress,
+        // Chain ID of the token.
+        token_chain: u16,
+        // Address of the recipient. Left-zero-padded if shorter than 32 bytes.
+        recipient: ExternalAddress,
+        // Chain ID of the recipient.
+        recipient_chain: u16,
+        // Amount of tokens that the user is willing to pay as relayer fee.
+        // Must be <= amount.
+        relayer_fee: NormalizedAmount,
+    }
+
+    /// Create new `Transfer`.
+    public(friend) fun new(
+        amount: NormalizedAmount,
+        token_address: ExternalAddress,
+        token_chain: u16,
+        recipient: ExternalAddress,
+        recipient_chain: u16,
+        relayer_fee: NormalizedAmount,
+    ): Transfer {
+        Transfer {
+            amount,
+            token_address,
+            token_chain,
+            recipient,
+            recipient_chain,
+            relayer_fee,
+        }
+    }
+
+    #[test_only]
+    public fun new_test_only(
+        amount: NormalizedAmount,
+        token_address: ExternalAddress,
+        token_chain: u16,
+        recipient: ExternalAddress,
+        recipient_chain: u16,
+        relayer_fee: NormalizedAmount,
+    ): Transfer {
+        new(
+            amount,
+            token_address,
+            token_chain,
+            recipient,
+            recipient_chain,
+            relayer_fee
+        )
+    }
+
+    /// Decompose `Transfer` into its members.
+    public(friend) fun unpack(
+        transfer: Transfer
+    ): (
+        NormalizedAmount,
+        ExternalAddress,
+        u16,
+        ExternalAddress,
+        u16,
+        NormalizedAmount
+    ) {
+        let Transfer {
+            amount,
+            token_address,
+            token_chain,
+            recipient,
+            recipient_chain,
+            relayer_fee,
+        } = transfer;
+
+        (
+            amount,
+            token_address,
+            token_chain,
+            recipient,
+            recipient_chain,
+            relayer_fee
+        )
+    }
+
+    #[test_only]
+    public fun unpack_test_only(
+        transfer: Transfer
+    ): (
+        NormalizedAmount,
+        ExternalAddress,
+        u16,
+        ExternalAddress,
+        u16,
+        NormalizedAmount
+    ) {
+        unpack(transfer)
+    }
+
+    /// Decode Wormhole message payload as `Transfer`.
+    public(friend) fun deserialize(buf: vector<u8>): Transfer {
+        let cur = cursor::new(buf);
+        assert!(bytes::take_u8(&mut cur) == PAYLOAD_ID, E_INVALID_PAYLOAD);
+
+        let amount = normalized_amount::take_bytes(&mut cur);
+        let token_address = external_address::take_bytes(&mut cur);
+        let token_chain = bytes::take_u16_be(&mut cur);
+        let recipient = external_address::take_bytes(&mut cur);
+        let recipient_chain = bytes::take_u16_be(&mut cur);
+        let relayer_fee = normalized_amount::take_bytes(&mut cur);
+        cursor::destroy_empty(cur);
+
+        Transfer {
+            amount,
+            token_address,
+            token_chain,
+            recipient,
+            recipient_chain,
+            relayer_fee,
+        }
+    }
+
+    #[test_only]
+    public fun deserialize_test_only(buf: vector<u8>): Transfer {
+        deserialize(buf)
+    }
+
+    /// Encode `Transfer` for Wormhole message payload.
+    public(friend) fun serialize(transfer: Transfer): vector<u8> {
+        let (
+            amount,
+            token_address,
+            token_chain,
+            recipient,
+            recipient_chain,
+            relayer_fee,
+        ) = unpack(transfer);
+
+        let buf = vector::empty<u8>();
+        bytes::push_u8(&mut buf, PAYLOAD_ID);
+        vector::append(&mut buf, normalized_amount::to_bytes(amount));
+        vector::append(&mut buf, external_address::to_bytes(token_address));
+        bytes::push_u16_be(&mut buf, token_chain);
+        vector::append(&mut buf, external_address::to_bytes(recipient));
+        bytes::push_u16_be(&mut buf, recipient_chain);
+        vector::append(&mut buf, normalized_amount::to_bytes(relayer_fee));
+
+        buf
+    }
+
+    #[test_only]
+    public fun serialize_test_only(transfer: Transfer): vector<u8> {
+        serialize(transfer)
+    }
+
+    #[test_only]
+    public fun amount(self: &Transfer): NormalizedAmount {
+        self.amount
+    }
+
+    #[test_only]
+    public fun raw_amount(self: &Transfer, decimals: u8): u64 {
+        normalized_amount::to_raw(self.amount, decimals)
+    }
+
+    #[test_only]
+    public fun token_address(self: &Transfer): ExternalAddress {
+        self.token_address
+    }
+
+    #[test_only]
+    public fun token_chain(self: &Transfer): u16 {
+        self.token_chain
+    }
+
+    #[test_only]
+    public fun recipient(self: &Transfer): ExternalAddress {
+        self.recipient
+    }
+
+    #[test_only]
+    public fun recipient_as_address(self: &Transfer): address {
+        external_address::to_address(self.recipient)
+    }
+
+    #[test_only]
+    public fun recipient_chain(self: &Transfer): u16 {
+        self.recipient_chain
+    }
+
+    #[test_only]
+    public fun relayer_fee(self: &Transfer): NormalizedAmount {
+        self.relayer_fee
+    }
+
+    #[test_only]
+    public fun raw_relayer_fee(self: &Transfer, decimals: u8): u64 {
+        normalized_amount::to_raw(self.relayer_fee, decimals)
+    }
+
+    #[test_only]
+    public fun destroy(transfer: Transfer) {
+        unpack(transfer);
+    }
+
+    #[test_only]
+    public fun payload_id(): u8 {
+        PAYLOAD_ID
+    }
+}
+
+#[test_only]
+module token_bridge::transfer_tests {
+    use std::vector::{Self};
+    use wormhole::external_address::{Self};
+
+    use token_bridge::dummy_message::{Self};
+    use token_bridge::transfer::{Self};
+    use token_bridge::normalized_amount::{Self};
+
+    #[test]
+    fun test_serialize_deserialize() {
+        let decimals = 8;
+        let expected_amount = normalized_amount::from_raw(234567890, decimals);
+        let expected_token_address = external_address::from_address(@0xbeef);
+        let expected_token_chain = 1;
+        let expected_recipient = external_address::from_address(@0xcafe);
+        let expected_recipient_chain = 7;
+        let expected_relayer_fee =
+            normalized_amount::from_raw(123456789, decimals);
+
+        let serialized =
+            transfer::serialize_test_only(
+                transfer::new_test_only(
+                    expected_amount,
+                    expected_token_address,
+                    expected_token_chain,
+                    expected_recipient,
+                    expected_recipient_chain,
+                    expected_relayer_fee,
+                )
+            );
+        assert!(serialized == dummy_message::encoded_transfer(), 0);
+
+        let (
+            amount,
+            token_address,
+            token_chain,
+            recipient,
+            recipient_chain,
+            relayer_fee
+        ) = transfer::unpack_test_only(
+            transfer::deserialize_test_only(serialized)
+        );
+        assert!(amount == expected_amount, 0);
+        assert!(token_address == expected_token_address, 0);
+        assert!(token_chain == expected_token_chain, 0);
+        assert!(recipient == expected_recipient, 0);
+        assert!(recipient_chain == expected_recipient_chain, 0);
+        assert!(relayer_fee == expected_relayer_fee, 0);
+    }
+
+    #[test]
+    #[expected_failure(abort_code = transfer::E_INVALID_PAYLOAD)]
+    fun test_cannot_deserialize_invalid_payload() {
+        let invalid_payload = dummy_message::encoded_transfer_with_payload();
+
+        // Show that the first byte is not the expected payload ID.
+        assert!(
+            *vector::borrow(&invalid_payload, 0) != transfer::payload_id(),
+            0
+        );
+
+        // You shall not pass!
+        let parsed = transfer::deserialize_test_only(invalid_payload);
+
+        // Clean up.
+        transfer::destroy(parsed);
+
+        abort 42
+    }
+}

+ 352 - 0
target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/messages/transfer_with_payload.move

@@ -0,0 +1,352 @@
+// SPDX-License-Identifier: Apache 2
+
+/// This module implements serialization and deserialization for token transfer
+/// with an arbitrary payload. This message is a specific Wormhole message
+/// payload for Token Bridge.
+///
+/// In order to redeem these types of transfers, one must have an `EmitterCap`
+/// and the specified `redeemer` must agree with this capability.
+///
+/// See `transfer_tokens_with_payload` and `complete_transfer_with_payload`
+/// modules for more details.
+module token_bridge::transfer_with_payload {
+    use std::vector::{Self};
+    use sui::object::{Self, ID};
+    use wormhole::bytes::{Self};
+    use wormhole::cursor::{Self};
+    use wormhole::external_address::{Self, ExternalAddress};
+
+    use token_bridge::normalized_amount::{Self, NormalizedAmount};
+
+    friend token_bridge::transfer_tokens_with_payload;
+
+    /// Message payload is not `TransferWithPayload`.
+    const E_INVALID_PAYLOAD: u64 = 0;
+
+    /// Message identifier.
+    const PAYLOAD_ID: u8 = 3;
+
+    /// Container that warehouses transfer information, including arbitrary
+    /// payload.
+    ///
+    /// NOTE: This struct has `drop` because we do not want to require an
+    /// integrator receiving transfer information to have to manually destroy.
+    struct TransferWithPayload has drop {
+        // Transfer amount.
+        amount: NormalizedAmount,
+        // Address of the token. Left-zero-padded if shorter than 32 bytes.
+        token_address: ExternalAddress,
+        // Chain ID of the token.
+        token_chain: u16,
+        // A.K.A. 32-byte representation of `EmitterCap`.
+        redeemer: ExternalAddress,
+        // Chain ID of the redeemer.
+        redeemer_chain: u16,
+        // Address of the message sender.
+        sender: ExternalAddress,
+        // An arbitrary payload.
+        payload: vector<u8>,
+    }
+
+    /// Create new `TransferWithPayload` using a Token Bridge integrator's
+    /// emitter cap ID as the sender.
+    public(friend) fun new(
+        sender: ID,
+        amount: NormalizedAmount,
+        token_address: ExternalAddress,
+        token_chain: u16,
+        redeemer: ExternalAddress,
+        redeemer_chain: u16,
+        payload: vector<u8>
+    ): TransferWithPayload {
+        TransferWithPayload {
+            amount,
+            token_address,
+            token_chain,
+            redeemer,
+            redeemer_chain,
+            sender: external_address::from_id(sender),
+            payload
+        }
+    }
+
+    #[test_only]
+    public fun new_test_only(
+        sender: ID,
+        amount: NormalizedAmount,
+        token_address: ExternalAddress,
+        token_chain: u16,
+        redeemer: ExternalAddress,
+        redeemer_chain: u16,
+        payload: vector<u8>
+    ): TransferWithPayload {
+        new(
+            sender,
+            amount,
+            token_address,
+            token_chain,
+            redeemer,
+            redeemer_chain,
+            payload
+        )
+    }
+
+    /// Destroy `TransferWithPayload` and take only its payload.
+    public fun take_payload(transfer: TransferWithPayload): vector<u8> {
+        let TransferWithPayload {
+            amount: _,
+            token_address: _,
+            token_chain: _,
+            redeemer: _,
+            redeemer_chain: _,
+            sender: _,
+            payload
+         } = transfer;
+
+        payload
+    }
+
+    /// Retrieve normalized amount of token transfer.
+    public fun amount(self: &TransferWithPayload): NormalizedAmount {
+        self.amount
+    }
+
+    // Retrieve token's canonical address.
+    public fun token_address(self: &TransferWithPayload): ExternalAddress {
+        self.token_address
+    }
+
+    /// Retrieve token's canonical chain ID.
+    public fun token_chain(self: &TransferWithPayload): u16 {
+        self.token_chain
+    }
+
+    /// Retrieve redeemer.
+    public fun redeemer(self: &TransferWithPayload): ExternalAddress {
+        self.redeemer
+    }
+
+    // Retrieve redeemer as `ID`.
+    public fun redeemer_id(self: &TransferWithPayload): ID {
+        object::id_from_bytes(external_address::to_bytes(self.redeemer))
+    }
+
+    /// Retrieve target chain for redeemer.
+    public fun redeemer_chain(self: &TransferWithPayload): u16 {
+        self.redeemer_chain
+    }
+
+    /// Retrieve transfer sender.
+    public fun sender(self: &TransferWithPayload): ExternalAddress {
+        self.sender
+    }
+
+    /// Retrieve arbitrary payload.
+    public fun payload(self: &TransferWithPayload): vector<u8> {
+        self.payload
+    }
+
+    /// Decode Wormhole message payload as `TransferWithPayload`.
+    public fun deserialize(transfer: vector<u8>): TransferWithPayload {
+        let cur = cursor::new(transfer);
+        assert!(bytes::take_u8(&mut cur) == PAYLOAD_ID, E_INVALID_PAYLOAD);
+
+        let amount = normalized_amount::take_bytes(&mut cur);
+        let token_address = external_address::take_bytes(&mut cur);
+        let token_chain = bytes::take_u16_be(&mut cur);
+        let redeemer = external_address::take_bytes(&mut cur);
+        let redeemer_chain = bytes::take_u16_be(&mut cur);
+        let sender = external_address::take_bytes(&mut cur);
+
+        TransferWithPayload {
+            amount,
+            token_address,
+            token_chain,
+            redeemer,
+            redeemer_chain,
+            sender,
+            payload: cursor::take_rest(cur)
+        }
+    }
+
+    /// Encode `TransferWithPayload` for Wormhole message payload.
+    public fun serialize(transfer: TransferWithPayload): vector<u8> {
+        let TransferWithPayload {
+            amount,
+            token_address,
+            token_chain,
+            redeemer,
+            redeemer_chain,
+            sender,
+            payload
+         } = transfer;
+
+        let buf = vector::empty<u8>();
+        bytes::push_u8(&mut buf, PAYLOAD_ID);
+        bytes::push_u256_be(&mut buf, normalized_amount::to_u256(amount));
+        vector::append(&mut buf, external_address::to_bytes(token_address));
+        bytes::push_u16_be(&mut buf, token_chain);
+        vector::append(&mut buf, external_address::to_bytes(redeemer));
+        bytes::push_u16_be(&mut buf, redeemer_chain);
+        vector::append(&mut buf, external_address::to_bytes(sender));
+        vector::append(&mut buf, payload);
+
+        buf
+    }
+
+    #[test_only]
+    public fun destroy(transfer: TransferWithPayload) {
+        take_payload(transfer);
+    }
+
+    #[test_only]
+    public fun payload_id(): u8 {
+        PAYLOAD_ID
+    }
+}
+
+#[test_only]
+module token_bridge::transfer_with_payload_tests {
+    use std::vector::{Self};
+    use sui::object::{Self};
+    use wormhole::emitter::{Self};
+    use wormhole::external_address::{Self};
+
+    use token_bridge::dummy_message::{Self};
+    use token_bridge::normalized_amount::{Self};
+    use token_bridge::transfer_with_payload::{Self};
+
+    #[test]
+    fun test_serialize() {
+        let emitter_cap = emitter::dummy();
+        let amount = normalized_amount::from_raw(234567890, 8);
+        let token_address = external_address::from_address(@0xbeef);
+        let token_chain = 1;
+        let redeemer = external_address::from_address(@0xcafe);
+        let redeemer_chain = 7;
+        let payload = b"All your base are belong to us.";
+
+        let new_transfer =
+            transfer_with_payload::new_test_only(
+                object::id(&emitter_cap),
+                amount,
+                token_address,
+                token_chain,
+                redeemer,
+                redeemer_chain,
+                payload
+            );
+
+        // Verify getters.
+        assert!(
+            transfer_with_payload::amount(&new_transfer) == amount,
+            0
+        );
+        assert!(
+            transfer_with_payload::token_address(&new_transfer) == token_address,
+            0
+        );
+        assert!(
+            transfer_with_payload::token_chain(&new_transfer) == token_chain,
+            0
+        );
+        assert!(
+            transfer_with_payload::redeemer(&new_transfer) == redeemer,
+            0
+        );
+        assert!(
+            transfer_with_payload::redeemer_chain(&new_transfer) == redeemer_chain,
+            0
+        );
+        let expected_sender =
+            external_address::from_id(object::id(&emitter_cap));
+        assert!(
+            transfer_with_payload::sender(&new_transfer) == expected_sender,
+            0
+        );
+        assert!(
+            transfer_with_payload::payload(&new_transfer) == payload,
+            0
+        );
+
+        let serialized = transfer_with_payload::serialize(new_transfer);
+        let expected_serialized =
+            dummy_message::encoded_transfer_with_payload();
+        assert!(serialized == expected_serialized, 0);
+
+        // Clean up.
+        emitter::destroy_test_only(emitter_cap);
+    }
+
+    #[test]
+    fun test_deserialize() {
+        let expected_amount = normalized_amount::from_raw(234567890, 8);
+        let expected_token_address = external_address::from_address(@0xbeef);
+        let expected_token_chain = 1;
+        let expected_recipient = external_address::from_address(@0xcafe);
+        let expected_recipient_chain = 7;
+        let expected_sender =
+            external_address::from_address(
+                @0x381dd9078c322a4663c392761a0211b527c127b29583851217f948d62131f409
+            );
+        let expected_payload = b"All your base are belong to us.";
+
+        let parsed =
+            transfer_with_payload::deserialize(
+                dummy_message::encoded_transfer_with_payload()
+            );
+
+        // Verify getters.
+        assert!(
+            transfer_with_payload::amount(&parsed) == expected_amount,
+            0
+        );
+        assert!(
+            transfer_with_payload::token_address(&parsed) == expected_token_address,
+            0
+        );
+        assert!(
+            transfer_with_payload::token_chain(&parsed) == expected_token_chain,
+            0
+        );
+        assert!(
+            transfer_with_payload::redeemer(&parsed) == expected_recipient,
+            0
+        );
+        assert!(
+            transfer_with_payload::redeemer_chain(&parsed) == expected_recipient_chain,
+            0
+        );
+        assert!(
+            transfer_with_payload::sender(&parsed) == expected_sender,
+            0
+        );
+        assert!(
+            transfer_with_payload::payload(&parsed) == expected_payload,
+            0
+        );
+
+        let payload = transfer_with_payload::take_payload(parsed);
+        assert!(payload == expected_payload, 0);
+    }
+
+    #[test]
+    #[expected_failure(abort_code = transfer_with_payload::E_INVALID_PAYLOAD)]
+    fun test_cannot_deserialize_invalid_payload() {
+        let invalid_payload = token_bridge::dummy_message::encoded_transfer();
+
+        // Show that the first byte is not the expected payload ID.
+        assert!(
+            *vector::borrow(&invalid_payload, 0) != transfer_with_payload::payload_id(),
+            0
+        );
+
+        // You shall not pass!
+        let parsed = transfer_with_payload::deserialize(invalid_payload);
+
+        // Clean up.
+        transfer_with_payload::destroy(parsed);
+
+        abort 42
+    }
+}

+ 216 - 0
target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/migrate.move

@@ -0,0 +1,216 @@
+// SPDX-License-Identifier: Apache 2
+
+/// This module implements a public method intended to be called after an
+/// upgrade has been committed. The purpose is to add one-off migration logic
+/// that would alter Token Bridge `State`.
+///
+/// Included in migration is the ability to ensure that breaking changes for
+/// any of Token Bridge's methods by enforcing the current build version as
+/// their required minimum version.
+module token_bridge::migrate {
+    use sui::object::{ID};
+    use wormhole::governance_message::{Self, DecreeReceipt};
+
+    use token_bridge::state::{Self, State};
+    use token_bridge::upgrade_contract::{Self};
+
+    /// Event reflecting when `migrate` is successfully executed.
+    struct MigrateComplete has drop, copy {
+        package: ID
+    }
+
+    /// Execute migration logic. See `token_bridge::migrate` description for
+    /// more info.
+    public fun migrate(
+        token_bridge_state: &mut State,
+        receipt: DecreeReceipt<upgrade_contract::GovernanceWitness>
+    ) {
+        state::migrate__v__0_2_0(token_bridge_state);
+
+        // Perform standard migrate.
+        handle_migrate(token_bridge_state, receipt);
+
+        ////////////////////////////////////////////////////////////////////////
+        //
+        // NOTE: Put any one-off migration logic here.
+        //
+        // Most upgrades likely won't need to do anything, in which case the
+        // rest of this function's body may be empty. Make sure to delete it
+        // after the migration has gone through successfully.
+        //
+        // WARNING: The migration does *not* proceed atomically with the
+        // upgrade (as they are done in separate transactions).
+        // If the nature of this migration absolutely requires the migration to
+        // happen before certain other functionality is available, then guard
+        // that functionality with the `assert!` from above.
+        //
+        ////////////////////////////////////////////////////////////////////////
+
+        ////////////////////////////////////////////////////////////////////////
+    }
+
+    fun handle_migrate(
+        token_bridge_state: &mut State,
+        receipt: DecreeReceipt<upgrade_contract::GovernanceWitness>
+    ) {
+        // Update the version first.
+        //
+        // See `version_control` module for hard-coded configuration.
+        state::migrate_version(token_bridge_state);
+
+        // This capability ensures that the current build version is used.
+        let latest_only = state::assert_latest_only(token_bridge_state);
+
+        // Check if build digest is the current one.
+        let digest =
+            upgrade_contract::take_digest(
+                governance_message::payload(&receipt)
+            );
+        state::assert_authorized_digest(
+            &latest_only,
+            token_bridge_state,
+            digest
+        );
+        governance_message::destroy(receipt);
+
+        // Finally emit an event reflecting a successful migrate.
+        let package = state::current_package(&latest_only, token_bridge_state);
+        sui::event::emit(MigrateComplete { package });
+    }
+
+    #[test_only]
+    public fun set_up_migrate(token_bridge_state: &mut State) {
+        state::reverse_migrate__v__dummy(token_bridge_state);
+    }
+}
+
+#[test_only]
+module token_bridge::migrate_tests {
+    use sui::test_scenario::{Self};
+    use wormhole::wormhole_scenario::{
+        parse_and_verify_vaa,
+        verify_governance_vaa
+    };
+
+    use token_bridge::state::{Self};
+    use token_bridge::upgrade_contract::{Self};
+    use token_bridge::token_bridge_scenario::{
+        person,
+        return_state,
+        set_up_wormhole_and_token_bridge,
+        take_state,
+        upgrade_token_bridge
+    };
+
+    const UPGRADE_VAA: vector<u8> =
+        x"010000000001005b18d7710c442414435162dc2b46a421c3018a7ff03290eff112a828b7927e4a6a624174cb8385210f4684ac2dbde6e01e4046218f7f245af53e85c97a48e21a0100bc614e0000000000010000000000000000000000000000000000000000000000000000000000000004000000000000000101000000000000000000000000000000000000000000546f6b656e42726964676502001500000000000000000000000000000000000000000000006e6577206275696c64";
+
+    #[test]
+    fun test_migrate() {
+        use token_bridge::migrate::{migrate};
+
+        let user = person();
+        let my_scenario = test_scenario::begin(user);
+        let scenario = &mut my_scenario;
+
+        // Initialize Wormhole.
+        let wormhole_message_fee = 350;
+        set_up_wormhole_and_token_bridge(scenario, wormhole_message_fee);
+
+        // Next transaction should be conducted as an ordinary user.
+        test_scenario::next_tx(scenario, user);
+
+        // Upgrade (digest is just b"new build") for testing purposes.
+        upgrade_token_bridge(scenario);
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, user);
+
+        let token_bridge_state = take_state(scenario);
+
+        // Set up migrate (which prepares this package to be the same state as
+        // a previous release).
+        token_bridge::migrate::set_up_migrate(&mut token_bridge_state);
+
+        // Conveniently roll version back.
+        state::reverse_migrate_version(&mut token_bridge_state);
+
+        let verified_vaa = parse_and_verify_vaa(scenario, UPGRADE_VAA);
+        let ticket =
+            upgrade_contract::authorize_governance(&token_bridge_state);
+        let receipt =
+            verify_governance_vaa(scenario, verified_vaa, ticket);
+        // Simulate executing with an outdated build by upticking the minimum
+        // required version for `publish_message` to something greater than
+        // this build.
+        migrate(&mut token_bridge_state, receipt);
+
+        // Make sure we emitted an event.
+        let effects = test_scenario::next_tx(scenario, user);
+        assert!(test_scenario::num_user_events(&effects) == 1, 0);
+
+        // Clean up.
+        return_state(token_bridge_state);
+
+        // Done.
+        test_scenario::end(my_scenario);
+    }
+
+    #[test]
+    #[expected_failure(abort_code = wormhole::package_utils::E_INCORRECT_OLD_VERSION)]
+    /// ^ This expected error may change depending on the migration. In most
+    /// cases, this will abort with `wormhole::package_utils::E_INCORRECT_OLD_VERSION`.
+    fun test_cannot_migrate_again() {
+        use token_bridge::migrate::{migrate};
+
+        let user = person();
+        let my_scenario = test_scenario::begin(user);
+        let scenario = &mut my_scenario;
+
+        // Initialize Wormhole.
+        let wormhole_message_fee = 350;
+        set_up_wormhole_and_token_bridge(scenario, wormhole_message_fee);
+
+        // Next transaction should be conducted as an ordinary user.
+        test_scenario::next_tx(scenario, user);
+
+        // Upgrade (digest is just b"new build") for testing purposes.
+        upgrade_token_bridge(scenario);
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, user);
+
+        let token_bridge_state = take_state(scenario);
+
+        // Set up migrate (which prepares this package to be the same state as
+        // a previous release).
+        token_bridge::migrate::set_up_migrate(&mut token_bridge_state);
+
+        // Conveniently roll version back.
+        state::reverse_migrate_version(&mut token_bridge_state);
+
+        let verified_vaa = parse_and_verify_vaa(scenario, UPGRADE_VAA);
+        let ticket =
+            upgrade_contract::authorize_governance(&token_bridge_state);
+        let receipt =
+            verify_governance_vaa(scenario, verified_vaa, ticket);
+        // Simulate executing with an outdated build by upticking the minimum
+        // required version for `publish_message` to something greater than
+        // this build.
+        migrate(&mut token_bridge_state, receipt);
+
+        // Make sure we emitted an event.
+        let effects = test_scenario::next_tx(scenario, user);
+        assert!(test_scenario::num_user_events(&effects) == 1, 0);
+
+        let verified_vaa = parse_and_verify_vaa(scenario, UPGRADE_VAA);
+        let ticket =
+            upgrade_contract::authorize_governance(&token_bridge_state);
+        let receipt =
+            verify_governance_vaa(scenario, verified_vaa, ticket);
+        // You shall not pass!
+        migrate(&mut token_bridge_state, receipt);
+
+        abort 42
+    }
+}

+ 220 - 0
target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/resources/native_asset.move

@@ -0,0 +1,220 @@
+// SPDX-License-Identifier: Apache 2
+
+/// This module implements a custom type that keeps track of info relating to
+/// assets (coin types) native to Sui. Token Bridge takes custody of these
+/// assets when someone invokes a token transfer outbound. Likewise, Token
+/// Bridge releases some of its balance from its custody of when someone redeems
+/// an inbound token transfer intended for Sui.
+///
+/// See `token_registry` module for more details.
+module token_bridge::native_asset {
+    use sui::balance::{Self, Balance};
+    use sui::coin::{Self, CoinMetadata};
+    use sui::object::{Self};
+    use wormhole::external_address::{Self, ExternalAddress};
+    use wormhole::state::{chain_id};
+
+    friend token_bridge::complete_transfer;
+    friend token_bridge::token_registry;
+    friend token_bridge::transfer_tokens;
+
+    /// Container for storing canonical token address and custodied `Balance`.
+    struct NativeAsset<phantom C> has store {
+        custody: Balance<C>,
+        token_address: ExternalAddress,
+        decimals: u8
+    }
+
+    /// Token Bridge identifies native assets using `CoinMetadata` object `ID`.
+    /// This method converts this `ID` to `ExternalAddress`.
+    public fun canonical_address<C>(
+        metadata: &CoinMetadata<C>
+    ): ExternalAddress {
+        external_address::from_id(object::id(metadata))
+    }
+
+    /// Create new `NativeAsset`.
+    ///
+    /// NOTE: The canonical token address is determined by the coin metadata's
+    /// object ID.
+    public(friend) fun new<C>(metadata: &CoinMetadata<C>): NativeAsset<C> {
+        NativeAsset {
+            custody: balance::zero(),
+            token_address: canonical_address(metadata),
+            decimals: coin::get_decimals(metadata)
+        }
+    }
+
+    #[test_only]
+    public fun new_test_only<C>(metadata: &CoinMetadata<C>): NativeAsset<C> {
+        new(metadata)
+    }
+
+    /// Retrieve canonical token address.
+    public fun token_address<C>(self: &NativeAsset<C>): ExternalAddress {
+        self.token_address
+    }
+
+    /// Retrieve decimals, which originated from `CoinMetadata`.
+    public fun decimals<C>(self: &NativeAsset<C>): u8 {
+        self.decimals
+    }
+
+    /// Retrieve custodied `Balance` value.
+    public fun custody<C>(self: &NativeAsset<C>): u64 {
+        balance::value(&self.custody)
+    }
+
+    /// Retrieve canonical token chain ID (Sui's) and token address.
+    public fun canonical_info<C>(
+        self: &NativeAsset<C>
+    ): (u16, ExternalAddress) {
+        (chain_id(), self.token_address)
+    }
+
+    /// Deposit a given `Balance`. `Balance` originates from an outbound token
+    /// transfer for a native asset.
+    ///
+    /// See `transfer_tokens` module for more info.
+    public(friend) fun deposit<C>(
+        self: &mut NativeAsset<C>,
+        deposited: Balance<C>
+    ) {
+        balance::join(&mut self.custody, deposited);
+    }
+
+    #[test_only]
+    public fun deposit_test_only<C>(
+        self: &mut NativeAsset<C>,
+        deposited: Balance<C>
+    ) {
+        deposit(self, deposited)
+    }
+
+    /// Withdraw a given amount from custody. This amount is determiend by an
+    /// inbound token transfer payload for a native asset.
+    ///
+    /// See `complete_transfer` module for more info.
+    public(friend) fun withdraw<C>(
+        self: &mut NativeAsset<C>,
+        amount: u64
+    ): Balance<C> {
+        balance::split(&mut self.custody, amount)
+    }
+
+    #[test_only]
+    public fun withdraw_test_only<C>(
+        self: &mut NativeAsset<C>,
+        amount: u64
+    ): Balance<C> {
+        withdraw(self, amount)
+    }
+
+    #[test_only]
+    public fun destroy<C>(asset: NativeAsset<C>) {
+        let NativeAsset {
+            custody,
+            token_address: _,
+            decimals: _
+        } = asset;
+        balance::destroy_for_testing(custody);
+    }
+}
+
+#[test_only]
+module token_bridge::native_asset_tests {
+    use sui::balance::{Self};
+    use sui::coin::{Self};
+    use sui::object::{Self};
+    use sui::test_scenario::{Self};
+    use wormhole::external_address::{Self};
+    use wormhole::state::{chain_id};
+
+    use token_bridge::coin_native_10::{Self, COIN_NATIVE_10};
+    use token_bridge::native_asset::{Self};
+    use token_bridge::token_bridge_scenario::{person};
+
+    #[test]
+    /// In this test, we exercise all the functionalities of a native asset
+    /// object, including new, deposit, withdraw, to_token_info, as well as
+    /// getting fields token_address, decimals, balance.
+    fun test_native_asset() {
+        let caller = person();
+        let my_scenario = test_scenario::begin(caller);
+        let scenario = &mut my_scenario;
+
+        // Publish coin.
+        coin_native_10::init_test_only(test_scenario::ctx(scenario));
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, caller);
+
+        let coin_meta = coin_native_10::take_metadata(scenario);
+
+        // Make new.
+        let asset = native_asset::new_test_only(&coin_meta);
+
+        // Assert token address and decimals are correct.
+        let expected_token_address =
+            external_address::from_id(object::id(&coin_meta));
+        assert!(
+            native_asset::token_address(&asset) == expected_token_address,
+            0
+        );
+        assert!(
+            native_asset::decimals(&asset) == coin::get_decimals(&coin_meta),
+            0
+        );
+        assert!(native_asset::custody(&asset) == 0, 0);
+
+        // deposit some coins into the NativeAsset coin custody
+        let deposit_amount = 1000;
+        let (i, n) = (0, 8);
+        while (i < n) {
+            native_asset::deposit_test_only(
+                &mut asset,
+                balance::create_for_testing(
+                    deposit_amount
+                )
+            );
+            i = i + 1;
+        };
+        let total_deposited = n * deposit_amount;
+        assert!(native_asset::custody(&asset) == total_deposited, 0);
+
+        let withdraw_amount = 690;
+        let total_withdrawn = balance::zero();
+        let i = 0;
+        while (i < n) {
+            let withdrawn = native_asset::withdraw_test_only(
+                &mut asset,
+                withdraw_amount
+            );
+            assert!(balance::value(&withdrawn) == withdraw_amount, 0);
+            balance::join(&mut total_withdrawn, withdrawn);
+            i = i + 1;
+        };
+
+        // convert to token info and assert convrsion is correct
+        let (
+            token_chain,
+            token_address
+        ) = native_asset::canonical_info<COIN_NATIVE_10>(&asset);
+
+        assert!(token_chain == chain_id(), 0);
+        assert!(token_address == expected_token_address, 0);
+
+        // check that updated balance is correct
+        let expected_remaining = total_deposited - n * withdraw_amount;
+        let remaining = native_asset::custody(&asset);
+        assert!(remaining == expected_remaining, 0);
+
+        // Clean up.
+        coin_native_10::return_metadata(coin_meta);
+        balance::destroy_for_testing(total_withdrawn);
+        native_asset::destroy(asset);
+
+        // Done.
+        test_scenario::end(my_scenario);
+    }
+}

+ 784 - 0
target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/resources/token_registry.move

@@ -0,0 +1,784 @@
+// SPDX-License-Identifier: Apache 2
+
+/// This module implements a custom type that keeps track of both native and
+/// wrapped assets via dynamic fields. These dynamic fields are keyed off using
+/// coin types. This registry lives in `State`.
+///
+/// See `state` module for more details.
+module token_bridge::token_registry {
+    use std::ascii::{String};
+    use std::type_name::{Self};
+    use sui::coin::{TreasuryCap, CoinMetadata};
+    use sui::dynamic_field::{Self};
+    use sui::object::{Self, UID};
+    use sui::package::{UpgradeCap};
+    use sui::table::{Self, Table};
+    use sui::tx_context::{TxContext};
+    use wormhole::external_address::{Self, ExternalAddress};
+
+    use token_bridge::asset_meta::{Self, AssetMeta};
+    use token_bridge::native_asset::{Self, NativeAsset};
+    use token_bridge::wrapped_asset::{Self, WrappedAsset};
+
+    friend token_bridge::attest_token;
+    friend token_bridge::complete_transfer;
+    friend token_bridge::create_wrapped;
+    friend token_bridge::state;
+    friend token_bridge::transfer_tokens;
+
+    /// Asset is not registered yet.
+    const E_UNREGISTERED: u64 = 0;
+    /// Cannot register wrapped asset with same canonical token info.
+    const E_ALREADY_WRAPPED: u64 = 1;
+
+    /// This container is used to store native and wrapped assets of coin type
+    /// as dynamic fields under its `UID`. It also uses a mechanism to generate
+    /// arbitrary token addresses for native assets.
+    struct TokenRegistry has key, store {
+        id: UID,
+        num_wrapped: u64,
+        num_native: u64,
+        coin_types: Table<CoinTypeKey, String>
+    }
+
+    /// Container to provide convenient checking of whether an asset is wrapped
+    /// or native. `VerifiedAsset` can only be created either by passing in a
+    /// resource with `CoinType` or by verifying input token info against the
+    /// canonical info that exists in `TokenRegistry`.
+    ///
+    /// NOTE: This container can be dropped after it was created.
+    struct VerifiedAsset<phantom CoinType> has drop {
+        is_wrapped: bool,
+        chain: u16,
+        addr: ExternalAddress,
+        coin_decimals: u8
+    }
+
+    /// Wrapper of coin type to act as dynamic field key.
+    struct Key<phantom CoinType> has copy, drop, store {}
+
+    /// This struct is not used for anything within the contract. It exists
+    /// purely for someone with an RPC query to be able to fetch the type name
+    /// of coin type as a string via `TokenRegistry`.
+    struct CoinTypeKey has drop, copy, store {
+        chain: u16,
+        addr: vector<u8>
+    }
+
+    /// Create new `TokenRegistry`.
+    ///
+    /// See `setup` module for more info.
+    public(friend) fun new(ctx: &mut TxContext): TokenRegistry {
+        TokenRegistry {
+            id: object::new(ctx),
+            num_wrapped: 0,
+            num_native: 0,
+            coin_types: table::new(ctx)
+        }
+    }
+
+    #[test_only]
+    public fun new_test_only(ctx: &mut TxContext): TokenRegistry {
+        new(ctx)
+    }
+
+    /// Determine whether a particular coin type is registered.
+    public fun has<CoinType>(self: &TokenRegistry): bool {
+        dynamic_field::exists_(&self.id, Key<CoinType> {})
+    }
+
+    public fun assert_has<CoinType>(self: &TokenRegistry) {
+        assert!(has<CoinType>(self), E_UNREGISTERED);
+    }
+
+    public fun verified_asset<CoinType>(
+        self: &TokenRegistry
+    ): VerifiedAsset<CoinType> {
+        // We check specifically whether `CoinType` is associated with a dynamic
+        // field for `WrappedAsset`. This boolean will be used as the underlying
+        // value for `VerifiedAsset`.
+        let is_wrapped =
+            dynamic_field::exists_with_type<Key<CoinType>, WrappedAsset<CoinType>>(
+                &self.id,
+                Key {}
+            );
+        if (is_wrapped) {
+            let asset = borrow_wrapped<CoinType>(self);
+            let (chain, addr) = wrapped_asset::canonical_info(asset);
+            let coin_decimals = wrapped_asset::decimals(asset);
+
+            VerifiedAsset { is_wrapped, chain, addr, coin_decimals }
+        } else {
+            let asset = borrow_native<CoinType>(self);
+            let (chain, addr) = native_asset::canonical_info(asset);
+            let coin_decimals = native_asset::decimals(asset);
+
+            VerifiedAsset { is_wrapped, chain, addr, coin_decimals }
+        }
+    }
+
+    /// Determine whether a given `CoinType` is a wrapped asset.
+    public fun is_wrapped<CoinType>(verified: &VerifiedAsset<CoinType>): bool {
+        verified.is_wrapped
+    }
+
+    /// Retrieve canonical token chain ID from `VerifiedAsset`.
+    public fun token_chain<CoinType>(
+        verified: &VerifiedAsset<CoinType>
+    ): u16 {
+        verified.chain
+    }
+
+    /// Retrieve canonical token address from `VerifiedAsset`.
+    public fun token_address<CoinType>(
+        verified: &VerifiedAsset<CoinType>
+    ): ExternalAddress {
+        verified.addr
+    }
+
+    /// Retrieve decimals for a `VerifiedAsset`.
+    public fun coin_decimals<CoinType>(
+        verified: &VerifiedAsset<CoinType>
+    ): u8 {
+        verified.coin_decimals
+    }
+
+    /// Add a new wrapped asset to the registry and return the canonical token
+    /// address.
+    ///
+    /// See `state` module for more info.
+    public(friend) fun add_new_wrapped<CoinType>(
+        self: &mut TokenRegistry,
+        token_meta: AssetMeta,
+        coin_meta: &mut CoinMetadata<CoinType>,
+        treasury_cap: TreasuryCap<CoinType>,
+        upgrade_cap: UpgradeCap
+    ): ExternalAddress {
+        // Grab canonical token info.
+        let token_chain = asset_meta::token_chain(&token_meta);
+        let token_addr = asset_meta::token_address(&token_meta);
+
+        let coin_types = &mut self.coin_types;
+        let key =
+            CoinTypeKey {
+                chain: token_chain,
+                addr: external_address::to_bytes(token_addr)
+            };
+        // We need to make sure that the canonical token info has not been
+        // created for another coin type. This can happen if asset metadata
+        // is attested again from a foreign chain and another coin type is
+        // published using its VAA.
+        assert!(!table::contains(coin_types, key), E_ALREADY_WRAPPED);
+
+        // Now add the coin type.
+        table::add(
+            coin_types,
+            key,
+            type_name::into_string(type_name::get<CoinType>())
+        );
+
+        // NOTE: We do not assert that the coin type has not already been
+        // registered using !has<CoinType>(self) because `wrapped_asset::new`
+        // consumes `TreasuryCap`. This `TreasuryCap` is only created once for a particuar
+        // coin type via `create_wrapped::prepare_registration`. Because the
+        // `TreasuryCap` is globally unique and can only be created once, there is no
+        // risk that `add_new_wrapped` can be called again on the same coin
+        // type.
+        let asset =
+            wrapped_asset::new(
+                token_meta,
+                coin_meta,
+                treasury_cap,
+                upgrade_cap
+            );
+        dynamic_field::add(&mut self.id, Key<CoinType> {}, asset);
+        self.num_wrapped = self.num_wrapped + 1;
+
+        token_addr
+    }
+
+    #[test_only]
+    public fun add_new_wrapped_test_only<CoinType>(
+        self: &mut TokenRegistry,
+        token_meta: AssetMeta,
+        coin_meta: &mut CoinMetadata<CoinType>,
+        treasury_cap: TreasuryCap<CoinType>,
+        ctx: &mut TxContext
+    ): ExternalAddress {
+        add_new_wrapped(
+            self,
+            token_meta,
+            coin_meta,
+            treasury_cap,
+            sui::package::test_publish(
+                object::id_from_address(@token_bridge),
+                ctx
+            )
+        )
+    }
+
+    /// Add a new native asset to the registry and return the canonical token
+    /// address.
+    ///
+    /// NOTE: This method does not verify if `CoinType` is already in the
+    /// registry because `attest_token` already takes care of this check. If
+    /// This method were to be called on an already-registered asset, this
+    /// will throw with an error from `sui::dynamic_field` reflectina duplicate
+    /// field.
+    ///
+    /// See `attest_token` module for more info.
+    public(friend) fun add_new_native<CoinType>(
+        self: &mut TokenRegistry,
+        metadata: &CoinMetadata<CoinType>,
+    ): ExternalAddress {
+        // Create new native asset.
+        let asset = native_asset::new(metadata);
+        let token_addr = native_asset::token_address(&asset);
+
+        // Add to registry.
+        dynamic_field::add(&mut self.id, Key<CoinType> {}, asset);
+        self.num_native = self.num_native + 1;
+
+        // Now add the coin type.
+        table::add(
+            &mut self.coin_types,
+            CoinTypeKey {
+                chain: wormhole::state::chain_id(),
+                addr: external_address::to_bytes(token_addr)
+            },
+            type_name::into_string(type_name::get<CoinType>())
+        );
+
+        // Return the token address.
+        token_addr
+    }
+
+    #[test_only]
+    public fun add_new_native_test_only<CoinType>(
+        self: &mut TokenRegistry,
+        metadata: &CoinMetadata<CoinType>
+    ): ExternalAddress {
+        add_new_native(self, metadata)
+    }
+
+    public fun borrow_wrapped<CoinType>(
+        self: &TokenRegistry
+    ): &WrappedAsset<CoinType> {
+        dynamic_field::borrow(&self.id, Key<CoinType> {})
+    }
+
+    public(friend) fun borrow_mut_wrapped<CoinType>(
+        self: &mut TokenRegistry
+    ): &mut WrappedAsset<CoinType> {
+        dynamic_field::borrow_mut(&mut self.id, Key<CoinType> {})
+    }
+
+    #[test_only]
+    public fun borrow_mut_wrapped_test_only<CoinType>(
+        self: &mut TokenRegistry
+    ): &mut WrappedAsset<CoinType> {
+        borrow_mut_wrapped(self)
+    }
+
+    public fun borrow_native<CoinType>(
+        self: &TokenRegistry
+    ): &NativeAsset<CoinType> {
+        dynamic_field::borrow(&self.id, Key<CoinType> {})
+    }
+
+    public(friend) fun borrow_mut_native<CoinType>(
+        self: &mut TokenRegistry
+    ): &mut NativeAsset<CoinType> {
+        dynamic_field::borrow_mut(&mut self.id, Key<CoinType> {})
+    }
+
+    #[test_only]
+    public fun borrow_mut_native_test_only<CoinType>(
+        self: &mut TokenRegistry
+    ): &mut NativeAsset<CoinType> {
+        borrow_mut_native(self)
+    }
+
+    #[test_only]
+    public fun num_native(self: &TokenRegistry): u64 {
+        self.num_native
+    }
+
+    #[test_only]
+    public fun num_wrapped(self: &TokenRegistry): u64 {
+        self.num_wrapped
+    }
+
+    #[test_only]
+    public fun destroy(registry: TokenRegistry) {
+        let TokenRegistry {
+            id,
+            num_wrapped: _,
+            num_native: _,
+            coin_types
+        } = registry;
+        object::delete(id);
+        table::drop(coin_types);
+    }
+
+    #[test_only]
+    public fun coin_type_for(
+        self: &TokenRegistry,
+        chain: u16,
+        addr: vector<u8>
+    ): String {
+        *table::borrow(&self.coin_types, CoinTypeKey { chain, addr })
+    }
+}
+
+// In this test, we exercise the various functionalities of TokenRegistry,
+// including registering native and wrapped coins via add_new_native, and
+// add_new_wrapped, minting/burning/depositing/withdrawing said tokens, and also
+// storing metadata about the tokens.
+#[test_only]
+module token_bridge::token_registry_tests {
+    use std::type_name::{Self};
+    use sui::balance::{Self};
+    use sui::coin::{CoinMetadata};
+    use sui::test_scenario::{Self};
+    use wormhole::external_address::{Self};
+    use wormhole::state::{chain_id};
+
+    use token_bridge::asset_meta::{Self};
+    use token_bridge::coin_native_10::{Self, COIN_NATIVE_10};
+    use token_bridge::coin_wrapped_7::{Self, COIN_WRAPPED_7};
+    use token_bridge::native_asset::{Self};
+    use token_bridge::token_registry::{Self};
+    use token_bridge::token_bridge_scenario::{person};
+    use token_bridge::wrapped_asset::{Self};
+
+    struct SCAM_COIN has drop {}
+
+    #[test]
+    fun test_registered_tokens_native() {
+        let caller = person();
+        let my_scenario = test_scenario::begin(caller);
+        let scenario = &mut my_scenario;
+
+        // Initialize new coin.
+        coin_native_10::init_test_only(test_scenario::ctx(scenario));
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, caller);
+
+        // Initialize new token registry.
+        let registry =
+            token_registry::new_test_only(test_scenario::ctx(scenario));
+
+        // Check initial state.
+        assert!(token_registry::num_native(&registry) == 0, 0);
+        assert!(token_registry::num_wrapped(&registry) == 0, 0);
+
+        // Register native asset.
+        let coin_meta = coin_native_10::take_metadata(scenario);
+        let token_address =
+            token_registry::add_new_native_test_only(
+                &mut registry,
+                &coin_meta,
+            );
+        let expected_token_address =
+            native_asset::canonical_address(&coin_meta);
+        assert!(token_address == expected_token_address, 0);
+
+        // mint some native coins, then deposit them into the token registry
+        let deposit_amount = 69;
+        let (i, n) = (0, 8);
+        while (i < n) {
+            native_asset::deposit_test_only(
+                token_registry::borrow_mut_native_test_only(
+                    &mut registry,
+                ),
+                balance::create_for_testing<COIN_NATIVE_10>(
+                    deposit_amount
+                )
+            );
+            i = i + 1;
+        };
+        let total_deposited = n * deposit_amount;
+        {
+            let asset =
+                token_registry::borrow_native<COIN_NATIVE_10>(&registry);
+            assert!(native_asset::custody(asset) == total_deposited, 0);
+        };
+
+        // Withdraw and check balances.
+        let withdraw_amount = 420;
+        let withdrawn =
+            native_asset::withdraw_test_only(
+                token_registry::borrow_mut_native_test_only<COIN_NATIVE_10>(
+                    &mut registry
+                ),
+                withdraw_amount
+            );
+        assert!(balance::value(&withdrawn) == withdraw_amount, 0);
+        balance::destroy_for_testing(withdrawn);
+
+        let expected_remaining = total_deposited - withdraw_amount;
+        {
+            let asset =
+                token_registry::borrow_native<COIN_NATIVE_10>(&registry);
+            assert!(native_asset::custody(asset) == expected_remaining, 0);
+        };
+
+        // Verify registry values.
+        assert!(token_registry::num_native(&registry) == 1, 0);
+        assert!(token_registry::num_wrapped(&registry) == 0, 0);
+
+        let verified = token_registry::verified_asset<COIN_NATIVE_10>(&registry);
+        assert!(!token_registry::is_wrapped(&verified), 0);
+        assert!(token_registry::coin_decimals(&verified) == 10, 0);
+        assert!(token_registry::token_chain(&verified) == chain_id(), 0);
+        assert!(
+            token_registry::token_address(&verified) == expected_token_address,
+            0
+        );
+
+        // Check coin type.
+        let coin_type =
+            token_registry::coin_type_for(
+                &registry,
+                token_registry::token_chain(&verified),
+                external_address::to_bytes(
+                    token_registry::token_address(&verified)
+                )
+            );
+        assert!(
+            coin_type == type_name::into_string(type_name::get<COIN_NATIVE_10>()),
+            0
+        );
+
+        // Clean up.
+        token_registry::destroy(registry);
+        coin_native_10::return_metadata(coin_meta);
+
+        // Done.
+        test_scenario::end(my_scenario);
+    }
+
+    #[test]
+    fun test_registered_tokens_wrapped() {
+        let caller = person();
+        let my_scenario = test_scenario::begin(caller);
+        let scenario = &mut my_scenario;
+
+        // Initialize new coin.
+        let treasury_cap =
+            coin_wrapped_7::init_and_take_treasury_cap(
+                scenario,
+                caller
+            );
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, caller);
+
+        // Initialize new token registry.
+        let registry =
+            token_registry::new_test_only(test_scenario::ctx(scenario));
+
+        // Check initial state.
+        assert!(token_registry::num_wrapped(&registry) == 0, 0);
+        assert!(token_registry::num_native(&registry) == 0, 0);
+
+        let coin_meta = test_scenario::take_shared<CoinMetadata<COIN_WRAPPED_7>>(scenario);
+
+        // Register wrapped asset.
+        let wrapped_token_meta = coin_wrapped_7::token_meta();
+        token_registry::add_new_wrapped_test_only(
+            &mut registry,
+            wrapped_token_meta,
+            &mut coin_meta,
+            treasury_cap,
+            test_scenario::ctx(scenario)
+        );
+
+        test_scenario::return_shared(coin_meta);
+
+        // Mint wrapped coin via `WrappedAsset` several times.
+        let mint_amount = 420;
+        let total_minted = balance::zero();
+        let (i, n) = (0, 8);
+        while (i < n) {
+            let minted =
+                wrapped_asset::mint_test_only(
+                    token_registry::borrow_mut_wrapped_test_only<COIN_WRAPPED_7>(
+                        &mut registry,
+                    ),
+                    mint_amount
+                );
+            assert!(balance::value(&minted) == mint_amount, 0);
+            balance::join(&mut total_minted, minted);
+            i = i + 1;
+        };
+
+        let total_supply =
+            wrapped_asset::total_supply(
+                token_registry::borrow_wrapped<COIN_WRAPPED_7>(
+                    &registry
+                )
+            );
+        assert!(total_supply == balance::value(&total_minted), 0);
+
+        // withdraw, check value, and re-deposit native coins into registry
+        let burn_amount = 69;
+        let burned =
+            wrapped_asset::burn_test_only(
+                token_registry::borrow_mut_wrapped_test_only(&mut registry),
+                balance::split(&mut total_minted, burn_amount)
+            );
+        assert!(burned == burn_amount, 0);
+
+        let expected_remaining = total_supply - burn_amount;
+        let remaining =
+            wrapped_asset::total_supply(
+                token_registry::borrow_wrapped<COIN_WRAPPED_7>(
+                    &registry
+                )
+            );
+        assert!(remaining == expected_remaining, 0);
+        balance::destroy_for_testing(total_minted);
+
+        // Verify registry values.
+        assert!(token_registry::num_wrapped(&registry) == 1, 0);
+        assert!(token_registry::num_native(&registry) == 0, 0);
+
+
+        let verified = token_registry::verified_asset<COIN_WRAPPED_7>(&registry);
+        assert!(token_registry::is_wrapped(&verified), 0);
+        assert!(token_registry::coin_decimals(&verified) == 7, 0);
+
+        let wrapped_token_meta = coin_wrapped_7::token_meta();
+        assert!(
+            token_registry::token_chain(&verified) == asset_meta::token_chain(&wrapped_token_meta),
+            0
+        );
+        assert!(
+            token_registry::token_address(&verified) == asset_meta::token_address(&wrapped_token_meta),
+            0
+        );
+
+        // Check coin type.
+        let coin_type =
+            token_registry::coin_type_for(
+                &registry,
+                token_registry::token_chain(&verified),
+                external_address::to_bytes(
+                    token_registry::token_address(&verified)
+                )
+            );
+        assert!(
+            coin_type == type_name::into_string(type_name::get<COIN_WRAPPED_7>()),
+            0
+        );
+
+
+        // Clean up.
+        token_registry::destroy(registry);
+        asset_meta::destroy(wrapped_token_meta);
+
+        // Done.
+        test_scenario::end(my_scenario);
+    }
+
+    #[test]
+    #[expected_failure(abort_code = sui::dynamic_field::EFieldAlreadyExists)]
+    /// In this negative test case, we try to register a native token twice.
+    fun test_cannot_add_new_native_again() {
+        let caller = person();
+        let my_scenario = test_scenario::begin(caller);
+        let scenario = &mut my_scenario;
+
+        // Initialize new coin.
+        coin_native_10::init_test_only(test_scenario::ctx(scenario));
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, caller);
+
+        // Initialize new token registry.
+        let registry =
+            token_registry::new_test_only(test_scenario::ctx(scenario));
+
+        let coin_meta = coin_native_10::take_metadata(scenario);
+
+        // Add new native asset.
+        token_registry::add_new_native_test_only(
+            &mut registry,
+            &coin_meta
+        );
+
+        // You shall not pass!
+        //
+        // NOTE: We don't have a custom error for this. This will trigger a
+        // `sui::dynamic_field` error.
+        token_registry::add_new_native_test_only(
+            &mut registry,
+            &coin_meta
+        );
+
+        abort 42
+    }
+
+    #[test]
+    #[expected_failure(abort_code = sui::dynamic_field::EFieldTypeMismatch)]
+    // In this negative test case, we attempt to deposit a wrapped token into
+    // a TokenRegistry object, resulting in failure. A wrapped coin can
+    // only be minted and burned, not deposited.
+    fun test_cannot_deposit_wrapped_asset() {
+        let caller = person();
+        let my_scenario = test_scenario::begin(caller);
+        let scenario = &mut my_scenario;
+
+        let treasury_cap =
+            coin_wrapped_7::init_and_take_treasury_cap(
+                scenario,
+                caller
+            );
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, caller);
+
+        // Initialize new token registry.
+        let registry =
+            token_registry::new_test_only(test_scenario::ctx(scenario));
+
+        let coin_meta = test_scenario::take_shared<CoinMetadata<COIN_WRAPPED_7>>(scenario);
+
+        token_registry::add_new_wrapped_test_only(
+            &mut registry,
+            coin_wrapped_7::token_meta(),
+            &mut coin_meta,
+            treasury_cap,
+            test_scenario::ctx(scenario)
+        );
+
+        test_scenario::return_shared(coin_meta);
+
+        // Mint some wrapped coins and attempt to deposit balance.
+        let minted =
+            wrapped_asset::mint_test_only(
+                token_registry::borrow_mut_wrapped_test_only<COIN_WRAPPED_7>(
+                    &mut registry
+                ),
+                420420420
+            );
+
+        let verified = token_registry::verified_asset<COIN_WRAPPED_7>(&registry);
+        assert!(token_registry::is_wrapped(&verified), 0);
+
+        // You shall not pass!
+        //
+        // NOTE: We don't have a custom error for this. This will trigger a
+        // `sui::dynamic_field` error.
+        native_asset::deposit_test_only(
+            token_registry::borrow_mut_native_test_only<COIN_WRAPPED_7>(
+                &mut registry
+            ),
+            minted
+        );
+
+        abort 42
+    }
+
+    #[test]
+    #[expected_failure(abort_code = sui::dynamic_field::EFieldTypeMismatch)]
+    // In this negative test case, we attempt to deposit a wrapped token into
+    // a TokenRegistry object, resulting in failure. A wrapped coin can
+    // only be minted and burned, not deposited.
+    fun test_cannot_mint_native_asset() {
+        let caller = person();
+        let my_scenario = test_scenario::begin(caller);
+        let scenario = &mut my_scenario;
+
+        coin_native_10::init_test_only(test_scenario::ctx(scenario));
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, caller);
+
+        // Initialize new token registry.
+        let registry =
+            token_registry::new_test_only(test_scenario::ctx(scenario));
+
+        let coin_meta = coin_native_10::take_metadata(scenario);
+        token_registry::add_new_native_test_only(
+            &mut registry,
+            &coin_meta
+        );
+
+        // Show that this asset is not wrapped.
+        let verified = token_registry::verified_asset<COIN_NATIVE_10>(&registry);
+        assert!(!token_registry::is_wrapped(&verified), 0);
+
+        // You shall not pass!
+        //
+        // NOTE: We don't have a custom error for this. This will trigger a
+        // `sui::dynamic_field` error.
+        let minted =
+            wrapped_asset::mint_test_only(
+                token_registry::borrow_mut_wrapped_test_only<COIN_NATIVE_10>(
+                    &mut registry
+                ),
+                420
+            );
+
+        // Clean up.
+        balance::destroy_for_testing(minted);
+
+        abort 42
+    }
+
+    #[test]
+    #[expected_failure(abort_code = token_registry::E_ALREADY_WRAPPED)]
+    fun test_cannot_add_new_wrapped_with_same_canonical_info() {
+        let caller = person();
+        let my_scenario = test_scenario::begin(caller);
+        let scenario = &mut my_scenario;
+
+        // Initialize new coin.
+        let treasury_cap =
+            coin_wrapped_7::init_and_take_treasury_cap(
+                scenario,
+                caller
+            );
+
+        // Initialize other coin
+        coin_native_10::init_test_only(test_scenario::ctx(scenario));
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, caller);
+
+        // Initialize new token registry.
+        let registry =
+            token_registry::new_test_only(test_scenario::ctx(scenario));
+
+        let coin_meta = test_scenario::take_shared<CoinMetadata<COIN_WRAPPED_7>>(scenario);
+
+        // Register wrapped asset.
+        token_registry::add_new_wrapped_test_only(
+            &mut registry,
+            coin_wrapped_7::token_meta(),
+            &mut coin_meta,
+            treasury_cap,
+            test_scenario::ctx(scenario)
+        );
+
+        test_scenario::return_shared(coin_meta);
+
+        let coin_meta = coin_native_10::take_metadata(scenario);
+        let treasury_cap = coin_native_10::take_treasury_cap(scenario);
+
+        // You shall not pass!
+        token_registry::add_new_wrapped_test_only(
+            &mut registry,
+            coin_wrapped_7::token_meta(),
+            &mut coin_meta,
+            treasury_cap,
+            test_scenario::ctx(scenario)
+        );
+
+        abort 42
+    }
+}

+ 806 - 0
target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/resources/wrapped_asset.move

@@ -0,0 +1,806 @@
+// SPDX-License-Identifier: Apache 2
+
+/// This module implements two custom types relating to Token Bridge wrapped
+/// assets. These assets have been attested from foreign networks, whose
+/// metadata is stored in `ForeignInfo`. The Token Bridge contract is the
+/// only authority that can mint and burn these assets via `Supply`.
+///
+/// See `create_wrapped` and 'token_registry' modules for more details.
+module token_bridge::wrapped_asset {
+    use std::string::{String};
+    use sui::balance::{Self, Balance};
+    use sui::coin::{Self, TreasuryCap, CoinMetadata};
+    use sui::package::{Self, UpgradeCap};
+    use wormhole::external_address::{ExternalAddress};
+    use wormhole::state::{chain_id};
+
+    use token_bridge::string_utils;
+    use token_bridge::asset_meta::{Self, AssetMeta};
+    use token_bridge::normalized_amount::{cap_decimals};
+
+    friend token_bridge::complete_transfer;
+    friend token_bridge::create_wrapped;
+    friend token_bridge::token_registry;
+    friend token_bridge::transfer_tokens;
+
+    /// Token chain ID matching Sui's are not allowed.
+    const E_SUI_CHAIN: u64 = 0;
+    /// Canonical token info does match `AssetMeta` payload.
+    const E_ASSET_META_MISMATCH: u64 = 1;
+    /// Coin decimals don't match the VAA.
+    const E_DECIMALS_MISMATCH: u64 = 2;
+
+    /// Container storing foreign asset info.
+    struct ForeignInfo<phantom C> has store {
+        token_chain: u16,
+        token_address: ExternalAddress,
+        native_decimals: u8,
+        symbol: String
+    }
+
+    /// Container managing `ForeignInfo` and `TreasuryCap` for a wrapped asset
+    /// coin type.
+    struct WrappedAsset<phantom C> has store {
+        info: ForeignInfo<C>,
+        treasury_cap: TreasuryCap<C>,
+        decimals: u8,
+        upgrade_cap: UpgradeCap
+    }
+
+    /// Create new `WrappedAsset`.
+    ///
+    /// See `token_registry` module for more info.
+    public(friend) fun new<C>(
+        token_meta: AssetMeta,
+        coin_meta: &mut CoinMetadata<C>,
+        treasury_cap: TreasuryCap<C>,
+        upgrade_cap: UpgradeCap
+    ): WrappedAsset<C> {
+        // Verify that the upgrade cap is from the same package as coin type.
+        // This cap should not have been modified prior to creating this asset
+        // (i.e. should have the default upgrade policy and build version == 1).
+        wormhole::package_utils::assert_package_upgrade_cap<C>(
+            &upgrade_cap,
+            package::compatible_policy(),
+            1
+        );
+
+        let (
+            token_address,
+            token_chain,
+            native_decimals,
+            symbol,
+            name
+        ) = asset_meta::unpack(token_meta);
+
+        // Protect against adding `AssetMeta` which has Sui's chain ID.
+        assert!(token_chain != chain_id(), E_SUI_CHAIN);
+
+        // Set metadata.
+        coin::update_name(&treasury_cap, coin_meta, name);
+        coin::update_symbol(&treasury_cap, coin_meta, string_utils::to_ascii(&symbol));
+
+        let decimals = cap_decimals(native_decimals);
+
+        // Ensure that the `C` type has the right number of decimals. This is
+        // the only field in the coinmeta that cannot be changed after the fact,
+        // so we expect to receive one that already has the correct decimals
+        // set.
+        assert!(decimals == coin::get_decimals(coin_meta), E_DECIMALS_MISMATCH);
+
+        let info =
+            ForeignInfo {
+                token_address,
+                token_chain,
+                native_decimals,
+                symbol
+            };
+
+        WrappedAsset {
+            info,
+            treasury_cap,
+            decimals,
+            upgrade_cap
+        }
+    }
+
+    #[test_only]
+    public fun new_test_only<C>(
+        token_meta: AssetMeta,
+        coin_meta: &mut CoinMetadata<C>,
+        treasury_cap: TreasuryCap<C>,
+        upgrade_cap: UpgradeCap
+    ): WrappedAsset<C> {
+        new(token_meta, coin_meta, treasury_cap, upgrade_cap)
+    }
+
+    /// Update existing `ForeignInfo` using new `AssetMeta`.
+    ///
+    /// See `token_registry` module for more info.
+    public(friend) fun update_metadata<C>(
+        self: &mut WrappedAsset<C>,
+        coin_meta: &mut CoinMetadata<C>,
+        token_meta: AssetMeta
+    ) {
+        // NOTE: We ignore `native_decimals` because we do not enforce that
+        // an asset's decimals on a foreign network needs to stay the same.
+        let (
+            token_address,
+            token_chain,
+            _native_decimals,
+            symbol,
+            name
+        ) = asset_meta::unpack(token_meta);
+
+        // Verify canonical token info. Also check that the native decimals
+        // have not changed (because changing this info is not desirable, as
+        // this change means the supply changed on its native network).
+        //
+        // NOTE: This implicitly verifies that `token_chain` is not Sui's
+        // because this was checked already when the asset was first added.
+        let (expected_chain, expected_address) = canonical_info<C>(self);
+        assert!(
+            (
+                token_chain == expected_chain &&
+                token_address == expected_address
+            ),
+            E_ASSET_META_MISMATCH
+        );
+
+        // Finally only update the name and symbol.
+        self.info.symbol = symbol;
+        coin::update_name(&self.treasury_cap, coin_meta, name);
+        coin::update_symbol(&self.treasury_cap, coin_meta, string_utils::to_ascii(&symbol));
+    }
+
+    #[test_only]
+    public fun update_metadata_test_only<C>(
+        self: &mut WrappedAsset<C>,
+        coin_meta: &mut CoinMetadata<C>,
+        token_meta: AssetMeta
+    ) {
+        update_metadata(self, coin_meta, token_meta)
+    }
+
+    /// Retrieve immutable reference to `ForeignInfo`.
+    public fun info<C>(self: &WrappedAsset<C>): &ForeignInfo<C> {
+        &self.info
+    }
+
+    /// Retrieve canonical token chain ID from `ForeignInfo`.
+    public fun token_chain<C>(info: &ForeignInfo<C>): u16 {
+        info.token_chain
+    }
+
+    /// Retrieve canonical token address from `ForeignInfo`.
+    public fun token_address<C>(info: &ForeignInfo<C>): ExternalAddress {
+        info.token_address
+    }
+
+    /// Retrieve decimal amount from `ForeignInfo`.
+    ///
+    /// NOTE: This is for informational purposes. This decimal amount is not
+    /// used for any calculations.
+    public fun native_decimals<C>(info: &ForeignInfo<C>): u8 {
+        info.native_decimals
+    }
+
+    /// Retrieve asset's symbol (UTF-8) from `ForeignMetadata`.
+    ///
+    /// NOTE: This value can be updated.
+    public fun symbol<C>(info: &ForeignInfo<C>): String {
+        info.symbol
+    }
+
+    /// Retrieve total minted supply.
+    public fun total_supply<C>(self: &WrappedAsset<C>): u64 {
+        coin::total_supply(&self.treasury_cap)
+    }
+
+    /// Retrieve decimals for this wrapped asset. For any asset whose native
+    /// decimals is greater than the cap (8), this will be 8.
+    ///
+    /// See `normalized_amount` module for more info.
+    public fun decimals<C>(self: &WrappedAsset<C>): u8 {
+        self.decimals
+    }
+
+    /// Retrieve canonical token chain ID and token address.
+    public fun canonical_info<C>(
+        self: &WrappedAsset<C>
+    ): (u16, ExternalAddress) {
+        (self.info.token_chain, self.info.token_address)
+    }
+
+    /// Burn a given `Balance`. `Balance` originates from an outbound token
+    /// transfer for a wrapped asset.
+    ///
+    /// See `transfer_tokens` module for more info.
+    public(friend) fun burn<C>(
+        self: &mut WrappedAsset<C>,
+        burned: Balance<C>
+    ): u64 {
+        balance::decrease_supply(coin::supply_mut(&mut self.treasury_cap), burned)
+    }
+
+    #[test_only]
+    public fun burn_test_only<C>(
+        self: &mut WrappedAsset<C>,
+        burned: Balance<C>
+    ): u64 {
+        burn(self, burned)
+    }
+
+    /// Mint a given amount. This amount is determined by an inbound token
+    /// transfer payload for a wrapped asset.
+    ///
+    /// See `complete_transfer` module for more info.
+    public(friend) fun mint<C>(
+        self: &mut WrappedAsset<C>,
+        amount: u64
+    ): Balance<C> {
+        coin::mint_balance(&mut self.treasury_cap, amount)
+    }
+
+    #[test_only]
+    public fun mint_test_only<C>(
+        self: &mut WrappedAsset<C>,
+        amount: u64
+    ): Balance<C> {
+        mint(self, amount)
+    }
+
+    #[test_only]
+    public fun destroy<C>(asset: WrappedAsset<C>) {
+        let WrappedAsset {
+            info,
+            treasury_cap,
+            decimals: _,
+            upgrade_cap
+        } = asset;
+        sui::test_utils::destroy(treasury_cap);
+
+        let ForeignInfo {
+            token_chain: _,
+            token_address: _,
+            native_decimals: _,
+            symbol: _
+        } = info;
+
+        sui::package::make_immutable(upgrade_cap);
+    }
+}
+
+#[test_only]
+module token_bridge::wrapped_asset_tests {
+    use std::string::{Self};
+    use sui::balance::{Self};
+    use sui::coin::{Self, CoinMetadata};
+    use sui::object::{Self};
+    use sui::package::{Self};
+    use sui::test_scenario::{Self};
+    use wormhole::external_address::{Self};
+    use wormhole::state::{chain_id};
+
+    use token_bridge::asset_meta::{Self};
+    use token_bridge::string_utils;
+    use token_bridge::coin_native_10::{COIN_NATIVE_10, Self};
+    use token_bridge::coin_wrapped_12::{COIN_WRAPPED_12, Self};
+    use token_bridge::coin_wrapped_7::{COIN_WRAPPED_7, Self};
+    use token_bridge::token_bridge_scenario::{person};
+    use token_bridge::wrapped_asset::{Self};
+
+    #[test]
+    fun test_wrapped_asset_7() {
+        let caller = person();
+        let my_scenario = test_scenario::begin(caller);
+        let scenario = &mut my_scenario;
+
+        let parsed_meta = coin_wrapped_7::token_meta();
+        let expected_token_chain = asset_meta::token_chain(&parsed_meta);
+        let expected_token_address = asset_meta::token_address(&parsed_meta);
+        let expected_native_decimals =
+            asset_meta::native_decimals(&parsed_meta);
+        let expected_symbol = asset_meta::symbol(&parsed_meta);
+        let expected_name = asset_meta::name(&parsed_meta);
+
+        // Publish coin.
+        let treasury_cap =
+            coin_wrapped_7::init_and_take_treasury_cap(
+                scenario,
+                caller
+            );
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, caller);
+
+        // Upgrade cap belonging to coin type.
+        let upgrade_cap =
+            package::test_publish(
+                object::id_from_address(@token_bridge),
+                test_scenario::ctx(scenario)
+            );
+
+        let coin_meta: CoinMetadata<COIN_WRAPPED_7> = test_scenario::take_shared(scenario);
+
+        // Make new.
+        let asset =
+            wrapped_asset::new_test_only(
+                parsed_meta,
+                &mut coin_meta,
+                treasury_cap,
+                upgrade_cap
+            );
+
+        // Verify members.
+        let info = wrapped_asset::info(&asset);
+        assert!(
+            wrapped_asset::token_chain(info) == expected_token_chain,
+            0
+        );
+        assert!(
+            wrapped_asset::token_address(info) == expected_token_address,
+            0
+        );
+        assert!(
+            wrapped_asset::native_decimals(info) == expected_native_decimals,
+            0
+        );
+        assert!(coin::get_symbol(&coin_meta) == string_utils::to_ascii(&expected_symbol), 0);
+        assert!(coin::get_name(&coin_meta) == expected_name, 0);
+        assert!(wrapped_asset::total_supply(&asset) == 0, 0);
+
+        let (token_chain, token_address) =
+            wrapped_asset::canonical_info(&asset);
+        assert!(token_chain == expected_token_chain, 0);
+        assert!(token_address == expected_token_address, 0);
+
+        // Decimals are read from `CoinMetadata`, but in this case will agree
+        // with the value encoded in the VAA.
+        assert!(wrapped_asset::decimals(&asset) == expected_native_decimals, 0);
+        assert!(coin::get_decimals(&coin_meta) == expected_native_decimals, 0);
+
+        // Change name and symbol for update.
+        let new_symbol = std::ascii::into_bytes(coin::get_symbol(&coin_meta));
+
+        std::vector::append(&mut new_symbol, b"??? and profit");
+        assert!(new_symbol != *string::bytes(&expected_symbol), 0);
+
+        let new_name = coin::get_name(&coin_meta);
+        string::append(&mut new_name, string::utf8(b"??? and profit"));
+        assert!(new_name != expected_name, 0);
+
+        let updated_meta =
+            asset_meta::new(
+                expected_token_address,
+                expected_token_chain,
+                expected_native_decimals,
+                string::utf8(new_symbol),
+                new_name
+            );
+
+        // Update metadata now.
+        wrapped_asset::update_metadata_test_only(&mut asset, &mut coin_meta, updated_meta);
+
+        assert!(coin::get_symbol(&coin_meta) == std::ascii::string(new_symbol), 0);
+        assert!(coin::get_name(&coin_meta) == new_name, 0);
+
+        // Try to mint.
+        let mint_amount = 420;
+        let collected = balance::zero();
+        let (i, n) = (0, 8);
+        while (i < n) {
+            let minted =
+                wrapped_asset::mint_test_only(&mut asset, mint_amount);
+            assert!(balance::value(&minted) == mint_amount, 0);
+            balance::join(&mut collected, minted);
+            i = i + 1;
+        };
+        assert!(balance::value(&collected) == n * mint_amount, 0);
+        assert!(
+            wrapped_asset::total_supply(&asset) == balance::value(&collected),
+            0
+        );
+
+        // Now try to burn.
+        let burn_amount = 69;
+        let i = 0;
+        while (i < n) {
+            let burned = balance::split(&mut collected, burn_amount);
+            let check_amount =
+                wrapped_asset::burn_test_only(&mut asset, burned);
+            assert!(check_amount == burn_amount, 0);
+            i = i + 1;
+        };
+        let remaining = n * mint_amount - n * burn_amount;
+        assert!(wrapped_asset::total_supply(&asset) == remaining, 0);
+        assert!(balance::value(&collected) == remaining, 0);
+
+        test_scenario::return_shared(coin_meta);
+
+        // Clean up.
+        balance::destroy_for_testing(collected);
+        wrapped_asset::destroy(asset);
+
+        // Done.
+        test_scenario::end(my_scenario);
+    }
+
+    #[test]
+    fun test_wrapped_asset_12() {
+        let caller = person();
+        let my_scenario = test_scenario::begin(caller);
+        let scenario = &mut my_scenario;
+
+        let parsed_meta = coin_wrapped_12::token_meta();
+        let expected_token_chain = asset_meta::token_chain(&parsed_meta);
+        let expected_token_address = asset_meta::token_address(&parsed_meta);
+        let expected_native_decimals =
+            asset_meta::native_decimals(&parsed_meta);
+        let expected_symbol = asset_meta::symbol(&parsed_meta);
+        let expected_name = asset_meta::name(&parsed_meta);
+
+        // Publish coin.
+        let treasury_cap =
+            coin_wrapped_12::init_and_take_treasury_cap(
+                scenario,
+                caller
+            );
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, caller);
+
+        // Upgrade cap belonging to coin type.
+        let upgrade_cap =
+            package::test_publish(
+                object::id_from_address(@token_bridge),
+                test_scenario::ctx(scenario)
+            );
+
+        let coin_meta: CoinMetadata<COIN_WRAPPED_12> = test_scenario::take_shared(scenario);
+
+        // Make new.
+        let asset =
+            wrapped_asset::new_test_only(
+                parsed_meta,
+                &mut coin_meta,
+                treasury_cap,
+                upgrade_cap
+            );
+
+        // Verify members.
+        let info = wrapped_asset::info(&asset);
+        assert!(
+            wrapped_asset::token_chain(info) == expected_token_chain,
+            0
+        );
+        assert!(
+            wrapped_asset::token_address(info) == expected_token_address,
+            0
+        );
+        assert!(
+            wrapped_asset::native_decimals(info) == expected_native_decimals,
+            0
+        );
+        assert!(coin::get_symbol(&coin_meta) == string_utils::to_ascii(&expected_symbol), 0);
+        assert!(coin::get_name(&coin_meta) == expected_name, 0);
+        assert!(wrapped_asset::total_supply(&asset) == 0, 0);
+
+        let (token_chain, token_address) =
+            wrapped_asset::canonical_info(&asset);
+        assert!(token_chain == expected_token_chain, 0);
+        assert!(token_address == expected_token_address, 0);
+
+        // Decimals are read from `CoinMetadata`, but in this case will not
+        // agree with the value encoded in the VAA.
+        assert!(wrapped_asset::decimals(&asset) == 8, 0);
+        assert!(
+            coin::get_decimals(&coin_meta) == wrapped_asset::decimals(&asset),
+            0
+        );
+        assert!(wrapped_asset::decimals(&asset) != expected_native_decimals, 0);
+
+        // Change name and symbol for update.
+        let new_symbol = std::ascii::into_bytes(coin::get_symbol(&coin_meta));
+
+        std::vector::append(&mut new_symbol, b"??? and profit");
+        assert!(new_symbol != *string::bytes(&expected_symbol), 0);
+
+        let new_name = coin::get_name(&coin_meta);
+        string::append(&mut new_name, string::utf8(b"??? and profit"));
+        assert!(new_name != expected_name, 0);
+
+        let updated_meta =
+            asset_meta::new(
+                expected_token_address,
+                expected_token_chain,
+                expected_native_decimals,
+                string::utf8(new_symbol),
+                new_name
+            );
+
+        // Update metadata now.
+        wrapped_asset::update_metadata_test_only(&mut asset, &mut coin_meta, updated_meta);
+
+        assert!(coin::get_symbol(&coin_meta) == std::ascii::string(new_symbol), 0);
+        assert!(coin::get_name(&coin_meta) == new_name, 0);
+
+        // Try to mint.
+        let mint_amount = 420;
+        let collected = balance::zero();
+        let (i, n) = (0, 8);
+        while (i < n) {
+            let minted =
+                wrapped_asset::mint_test_only(&mut asset, mint_amount);
+            assert!(balance::value(&minted) == mint_amount, 0);
+            balance::join(&mut collected, minted);
+            i = i + 1;
+        };
+        assert!(balance::value(&collected) == n * mint_amount, 0);
+        assert!(
+            wrapped_asset::total_supply(&asset) == balance::value(&collected),
+            0
+        );
+
+        // Now try to burn.
+        let burn_amount = 69;
+        let i = 0;
+        while (i < n) {
+            let burned = balance::split(&mut collected, burn_amount);
+            let check_amount =
+                wrapped_asset::burn_test_only(&mut asset, burned);
+            assert!(check_amount == burn_amount, 0);
+            i = i + 1;
+        };
+        let remaining = n * mint_amount - n * burn_amount;
+        assert!(wrapped_asset::total_supply(&asset) == remaining, 0);
+        assert!(balance::value(&collected) == remaining, 0);
+
+        // Clean up.
+        balance::destroy_for_testing(collected);
+        wrapped_asset::destroy(asset);
+        test_scenario::return_shared(coin_meta);
+
+        // Done.
+        test_scenario::end(my_scenario);
+    }
+
+    #[test]
+    #[expected_failure(abort_code = wrapped_asset::E_SUI_CHAIN)]
+    // In this negative test case, we attempt to register a native coin as a
+    // wrapped coin.
+    fun test_cannot_new_sui_chain() {
+        let caller = person();
+        let my_scenario = test_scenario::begin(caller);
+        let scenario = &mut my_scenario;
+
+        // Initialize new coin type.
+        coin_native_10::init_test_only(test_scenario::ctx(scenario));
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, caller);
+
+        // Sui's chain ID is not allowed.
+        let invalid_meta =
+            asset_meta::new(
+                external_address::default(),
+                chain_id(),
+                10,
+                string::utf8(b""),
+                string::utf8(b"")
+            );
+
+        // Upgrade cap belonging to coin type.
+        let upgrade_cap =
+            package::test_publish(
+                object::id_from_address(@token_bridge),
+                test_scenario::ctx(scenario)
+            );
+
+        let treasury_cap = test_scenario::take_shared<coin::TreasuryCap<COIN_NATIVE_10>>(scenario);
+        let coin_meta = test_scenario::take_shared<CoinMetadata<COIN_NATIVE_10>>(scenario);
+
+        // You shall not pass!
+        let asset =
+            wrapped_asset::new_test_only(
+                invalid_meta,
+                &mut coin_meta,
+                treasury_cap,
+                upgrade_cap
+            );
+
+        // Clean up.
+        wrapped_asset::destroy(asset);
+
+        abort 42
+    }
+
+    #[test]
+    #[expected_failure(abort_code = wrapped_asset::E_ASSET_META_MISMATCH)]
+    /// In this negative test case, we attempt to update with a mismatching
+    /// chain.
+    fun test_cannot_update_metadata_asset_meta_mismatch_token_address() {
+        let caller = person();
+        let my_scenario = test_scenario::begin(caller);
+        let scenario = &mut my_scenario;
+
+        let parsed_meta = coin_wrapped_12::token_meta();
+        let expected_token_chain = asset_meta::token_chain(&parsed_meta);
+        let expected_token_address = asset_meta::token_address(&parsed_meta);
+        let expected_native_decimals =
+            asset_meta::native_decimals(&parsed_meta);
+
+        // Publish coin.
+        let treasury_cap =
+            coin_wrapped_12::init_and_take_treasury_cap(
+                scenario,
+                caller
+            );
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, caller);
+
+        // Upgrade cap belonging to coin type.
+        let upgrade_cap =
+            package::test_publish(
+                object::id_from_address(@token_bridge),
+                test_scenario::ctx(scenario)
+            );
+
+        let coin_meta = test_scenario::take_shared(scenario);
+
+        // Make new.
+        let asset =
+            wrapped_asset::new_test_only(
+                parsed_meta,
+                &mut coin_meta,
+                treasury_cap,
+                upgrade_cap
+            );
+
+        let invalid_meta =
+            asset_meta::new(
+                external_address::default(),
+                expected_token_chain,
+                expected_native_decimals,
+                string::utf8(b""),
+                string::utf8(b""),
+            );
+        assert!(
+            asset_meta::token_address(&invalid_meta) != expected_token_address,
+            0
+        );
+        assert!(
+            asset_meta::token_chain(&invalid_meta) == expected_token_chain,
+            0
+        );
+        assert!(
+            asset_meta::native_decimals(&invalid_meta) == expected_native_decimals,
+            0
+        );
+
+        // You shall not pass!
+        wrapped_asset::update_metadata_test_only(&mut asset, &mut coin_meta, invalid_meta);
+
+        // Clean up.
+        wrapped_asset::destroy(asset);
+
+        abort 42
+    }
+
+    #[test]
+    #[expected_failure(abort_code = wrapped_asset::E_ASSET_META_MISMATCH)]
+    /// In this negative test case, we attempt to update with a mismatching
+    /// chain.
+    fun test_cannot_update_metadata_asset_meta_mismatch_token_chain() {
+        let caller = person();
+        let my_scenario = test_scenario::begin(caller);
+        let scenario = &mut my_scenario;
+
+        let parsed_meta = coin_wrapped_12::token_meta();
+        let expected_token_chain = asset_meta::token_chain(&parsed_meta);
+        let expected_token_address = asset_meta::token_address(&parsed_meta);
+        let expected_native_decimals =
+            asset_meta::native_decimals(&parsed_meta);
+
+        // Publish coin.
+        let treasury_cap =
+            coin_wrapped_12::init_and_take_treasury_cap(
+                scenario,
+                caller
+            );
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, caller);
+
+        // Upgrade cap belonging to coin type.
+        let upgrade_cap =
+            package::test_publish(
+                object::id_from_address(@token_bridge),
+                test_scenario::ctx(scenario)
+            );
+
+        let coin_meta = test_scenario::take_shared(scenario);
+
+        // Make new.
+        let asset =
+            wrapped_asset::new_test_only(
+                parsed_meta,
+                &mut coin_meta,
+                treasury_cap,
+                upgrade_cap
+            );
+
+        let invalid_meta =
+            asset_meta::new(
+                expected_token_address,
+                chain_id(),
+                expected_native_decimals,
+                string::utf8(b""),
+                string::utf8(b""),
+            );
+        assert!(
+            asset_meta::token_address(&invalid_meta) == expected_token_address,
+            0
+        );
+        assert!(
+            asset_meta::token_chain(&invalid_meta) != expected_token_chain,
+            0
+        );
+        assert!(
+            asset_meta::native_decimals(&invalid_meta) == expected_native_decimals,
+            0
+        );
+
+        // You shall not pass!
+        wrapped_asset::update_metadata_test_only(&mut asset, &mut coin_meta, invalid_meta);
+
+        // Clean up.
+        wrapped_asset::destroy(asset);
+
+        abort 42
+    }
+
+    #[test]
+    #[expected_failure(
+        abort_code = wormhole::package_utils::E_INVALID_UPGRADE_CAP
+    )]
+    fun test_cannot_new_upgrade_cap_mismatch() {
+        let caller = person();
+        let my_scenario = test_scenario::begin(caller);
+        let scenario = &mut my_scenario;
+
+        // Publish coin.
+        let treasury_cap =
+            coin_wrapped_12::init_and_take_treasury_cap(
+                scenario,
+                caller
+            );
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, caller);
+
+        // Upgrade cap belonging to coin type.
+        let upgrade_cap =
+            package::test_publish(
+                object::id_from_address(@0xbadc0de),
+                test_scenario::ctx(scenario)
+            );
+
+        let coin_meta = test_scenario::take_shared(scenario);
+
+        // You shall not pass!
+        let asset =
+            wrapped_asset::new_test_only(
+                coin_wrapped_12::token_meta(),
+                &mut coin_meta,
+                treasury_cap,
+                upgrade_cap
+            );
+
+        // Clean up.
+        wrapped_asset::destroy(asset);
+
+        abort 42
+    }
+}

+ 78 - 0
target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/setup.move

@@ -0,0 +1,78 @@
+// SPDX-License-Identifier: Apache 2
+
+/// This module implements the mechanism to publish the Token Bridge contract
+/// and initialize `State` as a shared object.
+module token_bridge::setup {
+    use sui::object::{Self, UID};
+    use sui::package::{Self, UpgradeCap};
+    use sui::transfer::{Self};
+    use sui::tx_context::{Self, TxContext};
+    use wormhole::emitter::{EmitterCap};
+
+    use token_bridge::state::{Self};
+
+    /// Capability created at `init`, which will be destroyed once
+    /// `init_and_share_state` is called. This ensures only the deployer can
+    /// create the shared `State`.
+    struct DeployerCap has key, store {
+        id: UID
+    }
+
+    /// Called automatically when module is first published. Transfers
+    /// `DeployerCap` to sender.
+    ///
+    /// Only `setup::init_and_share_state` requires `DeployerCap`.
+    fun init(ctx: &mut TxContext) {
+        let deployer = DeployerCap { id: object::new(ctx) };
+        transfer::transfer(deployer, tx_context::sender(ctx));
+    }
+
+    #[test_only]
+    public fun init_test_only(ctx: &mut TxContext) {
+        // NOTE: This exists to mock up sui::package for proposed upgrades.
+        use sui::package::{Self};
+
+        init(ctx);
+
+        // This will be created and sent to the transaction sender
+        // automatically when the contract is published.
+        transfer::public_transfer(
+            package::test_publish(object::id_from_address(@token_bridge), ctx),
+            tx_context::sender(ctx)
+        );
+    }
+
+    #[allow(lint(share_owned))]
+    /// Only the owner of the `DeployerCap` can call this method. This
+    /// method destroys the capability and shares the `State` object.
+    public fun complete(
+        deployer: DeployerCap,
+        upgrade_cap: UpgradeCap,
+        emitter_cap: EmitterCap,
+        governance_chain: u16,
+        governance_contract: vector<u8>,
+        ctx: &mut TxContext
+    ) {
+        wormhole::package_utils::assert_package_upgrade_cap<DeployerCap>(
+            &upgrade_cap,
+            package::compatible_policy(),
+            1
+        );
+
+        // Destroy deployer cap.
+        let DeployerCap { id } = deployer;
+        object::delete(id);
+
+        // Share new state.
+        transfer::public_share_object(
+            state::new(
+                emitter_cap,
+                upgrade_cap,
+                governance_chain,
+                wormhole::external_address::new_nonzero(
+                    wormhole::bytes32::from_bytes(governance_contract)
+                ),
+                ctx
+            ));
+    }
+}

+ 396 - 0
target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/state.move

@@ -0,0 +1,396 @@
+// SPDX-License-Identifier: Apache 2
+
+/// This module implements the global state variables for Token Bridge as a
+/// shared object. The `State` object is used to perform anything that requires
+/// access to data that defines the Token Bridge contract. Examples of which are
+/// accessing registered assets and verifying `VAA` intended for Token Bridge by
+/// checking the emitter against its own registered emitters.
+module token_bridge::state {
+    use sui::object::{Self, ID, UID};
+    use sui::package::{UpgradeCap, UpgradeReceipt, UpgradeTicket};
+    use sui::table::{Self, Table};
+    use sui::tx_context::{TxContext};
+    use wormhole::bytes32::{Self, Bytes32};
+    use wormhole::consumed_vaas::{Self, ConsumedVAAs};
+    use wormhole::emitter::{EmitterCap};
+    use wormhole::external_address::{ExternalAddress};
+    use wormhole::package_utils::{Self};
+    use wormhole::publish_message::{MessageTicket};
+
+    use token_bridge::token_registry::{Self, TokenRegistry, VerifiedAsset};
+    use token_bridge::version_control::{Self};
+
+    /// Build digest does not agree with current implementation.
+    const E_INVALID_BUILD_DIGEST: u64 = 0;
+    /// Specified version does not match this build's version.
+    const E_VERSION_MISMATCH: u64 = 1;
+    /// Emitter has already been used to emit Wormhole messages.
+    const E_USED_EMITTER: u64 = 2;
+
+    friend token_bridge::attest_token;
+    friend token_bridge::complete_transfer;
+    friend token_bridge::complete_transfer_with_payload;
+    friend token_bridge::create_wrapped;
+    friend token_bridge::migrate;
+    friend token_bridge::register_chain;
+    friend token_bridge::setup;
+    friend token_bridge::transfer_tokens;
+    friend token_bridge::transfer_tokens_with_payload;
+    friend token_bridge::upgrade_contract;
+    friend token_bridge::vaa;
+
+    /// Capability reflecting that the current build version is used to invoke
+    /// state methods.
+    struct LatestOnly has drop {}
+
+    /// Container for all state variables for Token Bridge.
+    struct State has key, store {
+        id: UID,
+
+        /// Governance chain ID.
+        governance_chain: u16,
+
+        /// Governance contract address.
+        governance_contract: ExternalAddress,
+
+        /// Set of consumed VAA hashes.
+        consumed_vaas: ConsumedVAAs,
+
+        /// Emitter capability required to publish Wormhole messages.
+        emitter_cap: EmitterCap,
+
+        /// Registry for foreign Token Bridge contracts.
+        emitter_registry: Table<u16, ExternalAddress>,
+
+        /// Registry for native and wrapped assets.
+        token_registry: TokenRegistry,
+
+        /// Upgrade capability.
+        upgrade_cap: UpgradeCap
+    }
+
+    /// Create new `State`. This is only executed using the `setup` module.
+    public(friend) fun new(
+        emitter_cap: EmitterCap,
+        upgrade_cap: UpgradeCap,
+        governance_chain: u16,
+        governance_contract: ExternalAddress,
+        ctx: &mut TxContext
+    ): State {
+        assert!(wormhole::emitter::sequence(&emitter_cap) == 0, E_USED_EMITTER);
+
+        let state = State {
+            id: object::new(ctx),
+            governance_chain,
+            governance_contract,
+            consumed_vaas: consumed_vaas::new(ctx),
+            emitter_cap,
+            emitter_registry: table::new(ctx),
+            token_registry: token_registry::new(ctx),
+            upgrade_cap
+        };
+
+        // Set first version and initialize package info. This will be used for
+        // emitting information of successful migrations.
+        let upgrade_cap = &state.upgrade_cap;
+        package_utils::init_package_info(
+            &mut state.id,
+            version_control::current_version(),
+            upgrade_cap
+        );
+
+        state
+    }
+
+    ////////////////////////////////////////////////////////////////////////////
+    //
+    //  Simple Getters
+    //
+    //  These methods do not require `LatestOnly` for access. Anyone is free to
+    //  access these values.
+    //
+    ////////////////////////////////////////////////////////////////////////////
+
+    /// Retrieve governance module name.
+    public fun governance_module(): Bytes32 {
+        // A.K.A. "TokenBridge".
+        bytes32::new(
+            x"000000000000000000000000000000000000000000546f6b656e427269646765"
+        )
+    }
+
+    /// Retrieve governance chain ID, which is governance's emitter chain ID.
+    public fun governance_chain(self: &State): u16 {
+        self.governance_chain
+    }
+
+    /// Retrieve governance emitter address.
+    public fun governance_contract(self: &State): ExternalAddress {
+        self.governance_contract
+    }
+
+    /// Retrieve immutable reference to `TokenRegistry`.
+    public fun borrow_token_registry(
+        self: &State
+    ): &TokenRegistry {
+        &self.token_registry
+    }
+
+    public fun borrow_emitter_registry(
+        self: &State
+    ): &Table<u16, ExternalAddress> {
+        &self.emitter_registry
+    }
+
+    public fun verified_asset<CoinType>(
+        self: &State
+    ): VerifiedAsset<CoinType> {
+        token_registry::assert_has<CoinType>(&self.token_registry);
+        token_registry::verified_asset(&self.token_registry)
+    }
+
+    #[test_only]
+    public fun borrow_mut_token_registry_test_only(
+        self: &mut State
+    ): &mut TokenRegistry {
+        borrow_mut_token_registry(&assert_latest_only(self), self)
+    }
+
+    #[test_only]
+    public fun migrate_version_test_only<Old: store + drop, New: store + drop>(
+        self: &mut State,
+        old_version: Old,
+        new_version: New
+    ) {
+        wormhole::package_utils::update_version_type_test_only(
+            &mut self.id,
+            old_version,
+            new_version
+        );
+    }
+
+    #[test_only]
+    public fun test_upgrade(self: &mut State) {
+        let test_digest = bytes32::from_bytes(b"new build");
+        let ticket = authorize_upgrade(self, test_digest);
+        let receipt = sui::package::test_upgrade(ticket);
+        commit_upgrade(self, receipt);
+    }
+
+    #[test_only]
+    public fun reverse_migrate_version(self: &mut State) {
+        package_utils::update_version_type_test_only(
+            &mut self.id,
+            version_control::current_version(),
+            version_control::previous_version()
+        );
+    }
+
+    ////////////////////////////////////////////////////////////////////////////
+    //
+    //  Privileged `State` Access
+    //
+    //  This section of methods require a `LatestOnly`, which can only be
+    //  created within the Token Bridge package. This capability allows special
+    //  access to the `State` object where we require that the latest build is
+    //  used for these interactions.
+    //
+    //  NOTE: A lot of these methods are still marked as `(friend)` as a safety
+    //  precaution. When a package is upgraded, friend modifiers can be
+    //  removed.
+    //
+    ////////////////////////////////////////////////////////////////////////////
+
+    /// Obtain a capability to interact with `State` methods. This method checks
+    /// that we are running the current build.
+    ///
+    /// NOTE: This method allows caching the current version check so we avoid
+    /// multiple checks to dynamic fields.
+    public(friend) fun assert_latest_only(self: &State): LatestOnly {
+        package_utils::assert_version(
+            &self.id,
+            version_control::current_version()
+        );
+
+        LatestOnly {}
+    }
+
+    /// Obtain a capability to interact with `State` methods. This method checks
+    /// that we are running the current build and that the specified `Version`
+    /// equals the current version. This method is useful when external modules
+    /// invoke Token Bridge and we need to check that the external module's
+    /// version is up-to-date (e.g. `create_wrapped::prepare_registration`).
+    ///
+    /// NOTE: This method allows caching the current version check so we avoid
+    /// multiple checks to dynamic fields.
+    public(friend) fun assert_latest_only_specified<Version>(
+        self: &State
+    ): LatestOnly {
+        use std::type_name::{get};
+
+        // Explicitly check the type names.
+        let current_type =
+            package_utils::type_of_version(version_control::current_version());
+        assert!(current_type == get<Version>(), E_VERSION_MISMATCH);
+
+        assert_latest_only(self)
+    }
+
+    /// Store `VAA` hash as a way to claim a VAA. This method prevents a VAA
+    /// from being replayed.
+    public(friend) fun borrow_mut_consumed_vaas(
+        _: &LatestOnly,
+        self: &mut State
+    ): &mut ConsumedVAAs {
+        borrow_mut_consumed_vaas_unchecked(self)
+    }
+
+    /// Store `VAA` hash as a way to claim a VAA. This method prevents a VAA
+    /// from being replayed.
+    ///
+    /// NOTE: This method does not require `LatestOnly`. Only methods in the
+    /// `upgrade_contract` module requires this to be unprotected to prevent
+    /// a corrupted upgraded contract from bricking upgradability.
+    public(friend) fun borrow_mut_consumed_vaas_unchecked(
+        self: &mut State
+    ): &mut ConsumedVAAs {
+        &mut self.consumed_vaas
+    }
+
+    /// Publish Wormhole message using Token Bridge's `EmitterCap`.
+    public(friend) fun prepare_wormhole_message(
+        _: &LatestOnly,
+        self: &mut State,
+        nonce: u32,
+        payload: vector<u8>
+    ): MessageTicket {
+        wormhole::publish_message::prepare_message(
+            &mut self.emitter_cap,
+            nonce,
+            payload,
+        )
+    }
+
+    /// Retrieve mutable reference to `TokenRegistry`.
+    public(friend) fun borrow_mut_token_registry(
+        _: &LatestOnly,
+        self: &mut State
+    ): &mut TokenRegistry {
+        &mut self.token_registry
+    }
+
+    public(friend) fun borrow_mut_emitter_registry(
+        _: &LatestOnly,
+        self: &mut State
+    ): &mut Table<u16, ExternalAddress> {
+        &mut self.emitter_registry
+    }
+
+    public(friend) fun current_package(_: &LatestOnly, self: &State): ID {
+        package_utils::current_package(&self.id)
+    }
+
+    ////////////////////////////////////////////////////////////////////////////
+    //
+    //  Upgradability
+    //
+    //  A special space that controls upgrade logic. These methods are invoked
+    //  via the `upgrade_contract` module.
+    //
+    //  Also in this section is managing contract migrations, which uses the
+    //  `migrate` module to officially roll state access to the latest build.
+    //  Only those methods that require `LatestOnly` will be affected by an
+    //  upgrade.
+    //
+    ////////////////////////////////////////////////////////////////////////////
+
+    /// Issue an `UpgradeTicket` for the upgrade.
+    ///
+    /// NOTE: The Sui VM performs a check that this method is executed from the
+    /// latest published package. If someone were to try to execute this using
+    /// a stale build, the transaction will revert with `PackageUpgradeError`,
+    /// specifically `PackageIDDoesNotMatch`.
+    public(friend) fun authorize_upgrade(
+        self: &mut State,
+        package_digest: Bytes32
+    ): UpgradeTicket {
+        let cap = &mut self.upgrade_cap;
+        package_utils::authorize_upgrade(&mut self.id, cap, package_digest)
+    }
+
+    /// Finalize the upgrade that ran to produce the given `receipt`.
+    ///
+    /// NOTE: The Sui VM performs a check that this method is executed from the
+    /// latest published package. If someone were to try to execute this using
+    /// a stale build, the transaction will revert with `PackageUpgradeError`,
+    /// specifically `PackageIDDoesNotMatch`.
+    public(friend) fun commit_upgrade(
+        self: &mut State,
+        receipt: UpgradeReceipt
+    ): (ID, ID) {
+        let cap = &mut self.upgrade_cap;
+        package_utils::commit_upgrade(&mut self.id, cap, receipt)
+    }
+
+    /// Method executed by the `migrate` module to roll access from one package
+    /// to another. This method will be called from the upgraded package.
+    public(friend) fun migrate_version(self: &mut State) {
+        package_utils::migrate_version(
+            &mut self.id,
+            version_control::previous_version(),
+            version_control::current_version()
+        );
+    }
+
+    /// As a part of the migration, we verify that the upgrade contract VAA's
+    /// encoded package digest used in `migrate` equals the one used to conduct
+    /// the upgrade.
+    public(friend) fun assert_authorized_digest(
+        _: &LatestOnly,
+        self: &State,
+        digest: Bytes32
+    ) {
+        let authorized = package_utils::authorized_digest(&self.id);
+        assert!(digest == authorized, E_INVALID_BUILD_DIGEST);
+    }
+
+    ////////////////////////////////////////////////////////////////////////////
+    //
+    //  Special State Interaction via Migrate
+    //
+    //  A VERY special space that manipulates `State` via calling `migrate`.
+    //
+    //  PLEASE KEEP ANY METHODS HERE AS FRIENDS. We want the ability to remove
+    //  these for future builds.
+    //
+    ////////////////////////////////////////////////////////////////////////////
+
+    /// This method is used to make modifications to `State` when `migrate` is
+    /// called. This method name should change reflecting which version this
+    /// contract is migrating to.
+    ///
+    /// NOTE: Please keep this method as public(friend) because we never want
+    /// to expose this method as a public method.
+    public(friend) fun migrate__v__0_2_0(_self: &mut State) {
+        // Intentionally do nothing.
+    }
+
+    #[test_only]
+    /// Bloody hack.
+    ///
+    /// This method is used to set up tests where we migrate to a new version,
+    /// which is meant to test that modules protected by version control will
+    /// break.
+    public fun reverse_migrate__v__dummy(_self: &mut State) {
+        // Intentionally do nothing.
+    }
+
+    ////////////////////////////////////////////////////////////////////////////
+    //
+    //  Deprecated
+    //
+    //  Dumping grounds for old structs and methods. These things should not
+    //  be used in future builds.
+    //
+    ////////////////////////////////////////////////////////////////////////////
+}

+ 165 - 0
target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/test/coin_native_10.move

@@ -0,0 +1,165 @@
+// SPDX-License-Identifier: Apache 2
+
+#[test_only]
+module token_bridge::coin_native_10 {
+    use std::option::{Self};
+    use sui::balance::{Self, Balance};
+    use sui::coin::{Self, CoinMetadata, TreasuryCap};
+    use sui::test_scenario::{Self, Scenario};
+    use sui::transfer::{Self};
+    use sui::tx_context::{TxContext};
+
+    use token_bridge::native_asset::{Self};
+    use token_bridge::state::{Self};
+    use token_bridge::token_registry::{Self};
+
+    struct COIN_NATIVE_10 has drop {}
+
+    // This module creates a Sui-native token for testing purposes,
+    // for example in complete_transfer, where we create a native coin,
+    // mint some and deposit in the token bridge, then complete transfer
+    // and ultimately transfer a portion of those native coins to a recipient.
+    fun init(coin_witness: COIN_NATIVE_10, ctx: &mut TxContext) {
+        let (
+            treasury_cap,
+            coin_metadata
+        ) =
+            coin::create_currency(
+                coin_witness,
+                10,
+                b"DEC10",
+                b"Decimals 10",
+                b"Coin with 10 decimals for testing purposes.",
+                option::none(),
+                ctx
+            );
+
+        // Allow us to mutate metadata if we need.
+        transfer::public_share_object(coin_metadata);
+
+        // Give everyone access to `TrasuryCap`.
+        transfer::public_share_object(treasury_cap);
+    }
+
+    #[test_only]
+    /// For a test scenario, register this native asset.
+    ///
+    /// NOTE: Even though this module is `#[test_only]`, this method is tagged
+    /// with the same macro  as a trick to allow another method within this
+    /// module to call `init` using OTW.
+    public fun init_and_register(scenario: &mut Scenario, caller: address) {
+        use token_bridge::token_bridge_scenario::{return_state, take_state};
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, caller);
+
+        // Publish coin.
+        init(COIN_NATIVE_10 {}, test_scenario::ctx(scenario));
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, caller);
+
+        let token_bridge_state = take_state(scenario);
+        let coin_meta = take_metadata(scenario);
+
+        // Register asset.
+        let registry =
+            state::borrow_mut_token_registry_test_only(&mut token_bridge_state);
+        token_registry::add_new_native_test_only(registry, &coin_meta);
+
+        // Clean up.
+        return_state(token_bridge_state);
+        return_metadata(coin_meta);
+    }
+
+    #[test_only]
+    public fun init_register_and_mint(
+        scenario: &mut Scenario,
+        caller: address,
+        amount: u64
+    ): Balance<COIN_NATIVE_10> {
+        // First publish and register.
+        init_and_register(scenario, caller);
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, caller);
+
+        // Mint.
+        balance::create_for_testing(amount)
+    }
+
+    #[test_only]
+    public fun init_register_and_deposit(
+        scenario: &mut Scenario,
+        caller: address,
+        amount: u64
+    ) {
+        use token_bridge::token_bridge_scenario::{return_state, take_state};
+
+        let minted = init_register_and_mint(scenario, caller, amount);
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, caller);
+
+        let token_bridge_state = take_state(scenario);
+        native_asset::deposit_test_only(
+            token_registry::borrow_mut_native_test_only(
+                state::borrow_mut_token_registry_test_only(
+                    &mut token_bridge_state
+                )
+            ),
+            minted
+        );
+
+        return_state(token_bridge_state);
+    }
+
+    #[test_only]
+    public fun init_test_only(ctx: &mut TxContext) {
+        init(COIN_NATIVE_10 {}, ctx);
+    }
+
+    public fun take_metadata(
+        scenario: &Scenario
+    ): CoinMetadata<COIN_NATIVE_10> {
+        test_scenario::take_shared(scenario)
+    }
+
+    public fun return_metadata(
+        metadata: CoinMetadata<COIN_NATIVE_10>
+    ) {
+        test_scenario::return_shared(metadata);
+    }
+
+    public fun take_treasury_cap(
+        scenario: &Scenario
+    ): TreasuryCap<COIN_NATIVE_10> {
+        test_scenario::take_shared(scenario)
+    }
+
+    public fun return_treasury_cap(
+        treasury_cap: TreasuryCap<COIN_NATIVE_10>
+    ) {
+        test_scenario::return_shared(treasury_cap);
+    }
+
+    public fun take_globals(
+        scenario: &Scenario
+    ): (
+        TreasuryCap<COIN_NATIVE_10>,
+        CoinMetadata<COIN_NATIVE_10>
+    ) {
+        (
+            take_treasury_cap(scenario),
+            take_metadata(scenario)
+        )
+    }
+
+    public fun return_globals(
+        treasury_cap: TreasuryCap<COIN_NATIVE_10>,
+        metadata: CoinMetadata<COIN_NATIVE_10>
+    ) {
+        return_treasury_cap(treasury_cap);
+        return_metadata(metadata);
+    }
+}

+ 165 - 0
target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/test/coin_native_4.move

@@ -0,0 +1,165 @@
+// SPDX-License-Identifier: Apache 2
+
+#[test_only]
+module token_bridge::coin_native_4 {
+    use std::option::{Self};
+    use sui::balance::{Self, Balance};
+    use sui::coin::{Self, CoinMetadata, TreasuryCap};
+    use sui::test_scenario::{Self, Scenario};
+    use sui::transfer::{Self};
+    use sui::tx_context::{TxContext};
+
+    use token_bridge::native_asset::{Self};
+    use token_bridge::state::{Self};
+    use token_bridge::token_registry::{Self};
+
+    struct COIN_NATIVE_4 has drop {}
+
+    // This module creates a Sui-native token for testing purposes,
+    // for example in complete_transfer, where we create a native coin,
+    // mint some and deposit in the token bridge, then complete transfer
+    // and ultimately transfer a portion of those native coins to a recipient.
+    fun init(coin_witness: COIN_NATIVE_4, ctx: &mut TxContext) {
+        let (
+            treasury_cap,
+            coin_metadata
+        ) =
+            coin::create_currency(
+                coin_witness,
+                4,
+                b"DEC4",
+                b"Decimals 4",
+                b"Coin with 4 decimals for testing purposes.",
+                option::none(),
+                ctx
+            );
+
+        // Let's make the metadata shared.
+        transfer::public_share_object(coin_metadata);
+
+        // Give everyone access to `TrasuryCap`.
+        transfer::public_share_object(treasury_cap);
+    }
+
+    #[test_only]
+    /// For a test scenario, register this native asset.
+    ///
+    /// NOTE: Even though this module is `#[test_only]`, this method is tagged
+    /// with the same macro  as a trick to allow another method within this
+    /// module to call `init` using OTW.
+    public fun init_and_register(scenario: &mut Scenario, caller: address) {
+        use token_bridge::token_bridge_scenario::{return_state, take_state};
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, caller);
+
+        // Publish coin.
+        init(COIN_NATIVE_4 {}, test_scenario::ctx(scenario));
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, caller);
+
+        let token_bridge_state = take_state(scenario);
+        let coin_meta = take_metadata(scenario);
+
+        // Register asset.
+        let registry =
+            state::borrow_mut_token_registry_test_only(&mut token_bridge_state);
+        token_registry::add_new_native_test_only(registry, &coin_meta);
+
+        // Clean up.
+        return_state(token_bridge_state);
+        return_metadata(coin_meta);
+    }
+
+    #[test_only]
+    public fun init_register_and_mint(
+        scenario: &mut Scenario,
+        caller: address,
+        amount: u64
+    ): Balance<COIN_NATIVE_4> {
+        // First publish and register.
+        init_and_register(scenario, caller);
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, caller);
+
+        // Mint.
+        balance::create_for_testing(amount)
+    }
+
+    #[test_only]
+    public fun init_register_and_deposit(
+        scenario: &mut Scenario,
+        caller: address,
+        amount: u64
+    ) {
+        use token_bridge::token_bridge_scenario::{return_state, take_state};
+
+        let minted = init_register_and_mint(scenario, caller, amount);
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, caller);
+
+        let token_bridge_state = take_state(scenario);
+        native_asset::deposit_test_only(
+            token_registry::borrow_mut_native_test_only(
+                state::borrow_mut_token_registry_test_only(
+                    &mut token_bridge_state
+                )
+            ),
+            minted
+        );
+
+        return_state(token_bridge_state);
+    }
+
+    #[test_only]
+    public fun init_test_only(ctx: &mut TxContext) {
+        init(COIN_NATIVE_4 {}, ctx);
+    }
+
+    public fun take_metadata(
+        scenario: &Scenario
+    ): CoinMetadata<COIN_NATIVE_4> {
+        test_scenario::take_shared(scenario)
+    }
+
+    public fun return_metadata(
+        metadata: CoinMetadata<COIN_NATIVE_4>
+    ) {
+        test_scenario::return_shared(metadata);
+    }
+
+    public fun take_treasury_cap(
+        scenario: &Scenario
+    ): TreasuryCap<COIN_NATIVE_4> {
+        test_scenario::take_shared(scenario)
+    }
+
+    public fun return_treasury_cap(
+        treasury_cap: TreasuryCap<COIN_NATIVE_4>
+    ) {
+        test_scenario::return_shared(treasury_cap);
+    }
+
+    public fun take_globals(
+        scenario: &Scenario
+    ): (
+        TreasuryCap<COIN_NATIVE_4>,
+        CoinMetadata<COIN_NATIVE_4>
+    ) {
+        (
+            take_treasury_cap(scenario),
+            take_metadata(scenario)
+        )
+    }
+
+    public fun return_globals(
+        treasury_cap: TreasuryCap<COIN_NATIVE_4>,
+        metadata: CoinMetadata<COIN_NATIVE_4>
+    ) {
+        return_treasury_cap(treasury_cap);
+        return_metadata(metadata);
+    }
+}

+ 193 - 0
target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/test/coin_wrapped_12.move

@@ -0,0 +1,193 @@
+// SPDX-License-Identifier: Apache 2
+
+#[test_only]
+module token_bridge::coin_wrapped_12 {
+    use sui::balance::{Balance};
+    use sui::package::{UpgradeCap};
+    use sui::coin::{CoinMetadata, TreasuryCap};
+    use sui::test_scenario::{Self, Scenario};
+    use sui::transfer::{Self};
+    use sui::tx_context::{Self, TxContext};
+
+    use token_bridge::asset_meta::{Self, AssetMeta};
+    use token_bridge::create_wrapped::{Self, WrappedAssetSetup};
+    use token_bridge::state::{Self};
+    use token_bridge::token_registry::{Self};
+    use token_bridge::wrapped_asset::{Self};
+
+    use token_bridge::version_control::{V__0_2_0 as V__CURRENT};
+
+    struct COIN_WRAPPED_12 has drop {}
+
+    const VAA: vector<u8> =
+        x"0100000000010080366065746148420220f25a6275097370e8db40984529a6676b7a5fc9feb11755ec49ca626b858ddfde88d15601f85ab7683c5f161413b0412143241c700aff010000000100000001000200000000000000000000000000000000000000000000000000000000deadbeef000000000150eb23000200000000000000000000000000000000000000000000000000000000beefface00020c424545460000000000000000000000000000000000000000000000000000000042656566206661636520546f6b656e0000000000000000000000000000000000";
+
+    const UPDATED_VAA: vector<u8> =
+        x"0100000000010062f4dcd21bbbc4af8b8baaa2da3a0b168efc4c975de5b828c7a3c710b67a0a0d476d10a74aba7a7867866daf97d1372d8e6ee62ccc5ae522e3e603c67fa23787000000000000000045000200000000000000000000000000000000000000000000000000000000deadbeef00000000000000010f0200000000000000000000000000000000000000000000000000000000beefface00020c424545463f3f3f20616e642070726f666974000000000000000000000000000042656566206661636520546f6b656e3f3f3f20616e642070726f666974000000";
+
+    fun init(witness: COIN_WRAPPED_12, ctx: &mut TxContext) {
+        let (
+            setup,
+            upgrade_cap
+        ) =
+            create_wrapped::new_setup_current(
+                witness,
+                8, // capped to 8
+                ctx
+            );
+        transfer::public_transfer(setup, tx_context::sender(ctx));
+        transfer::public_transfer(upgrade_cap, tx_context::sender(ctx));
+    }
+
+    #[test_only]
+    public fun init_test_only(ctx: &mut TxContext) {
+        init(COIN_WRAPPED_12 {}, ctx);
+    }
+
+
+    public fun encoded_vaa(): vector<u8> {
+        VAA
+    }
+
+    public fun encoded_updated_vaa(): vector<u8> {
+        UPDATED_VAA
+    }
+
+    #[allow(implicit_const_copy)]
+    public fun token_meta(): AssetMeta {
+        asset_meta::deserialize_test_only(
+            wormhole::vaa::peel_payload_from_vaa(&VAA)
+        )
+    }
+
+    #[allow(implicit_const_copy)]
+    public fun updated_token_meta(): AssetMeta {
+        asset_meta::deserialize_test_only(
+            wormhole::vaa::peel_payload_from_vaa(&UPDATED_VAA)
+        )
+    }
+
+    #[test_only]
+    /// for a test scenario, simply deploy the coin and expose `Supply`.
+    public fun init_and_take_treasury_cap(
+        scenario: &mut Scenario,
+        caller: address
+    ): TreasuryCap<COIN_WRAPPED_12> {
+        use token_bridge::create_wrapped;
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, caller);
+
+        // Publish coin.
+        init(COIN_WRAPPED_12 {}, test_scenario::ctx(scenario));
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, caller);
+
+        create_wrapped::take_treasury_cap(
+            test_scenario::take_from_sender(scenario)
+        )
+    }
+
+    #[test_only]
+    /// For a test scenario, register this wrapped asset.
+    ///
+    /// NOTE: Even though this module is `#[test_only]`, this method is tagged
+    /// with the same macro  as a trick to allow another method within this
+    /// module to call `init` using OTW.
+    public fun init_and_register(
+        scenario: &mut Scenario,
+        caller: address
+    ) {
+        use token_bridge::token_bridge_scenario::{return_state, take_state};
+        use wormhole::wormhole_scenario::{parse_and_verify_vaa};
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, caller);
+
+        // Publish coin.
+        init(COIN_WRAPPED_12 {}, test_scenario::ctx(scenario));
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, caller);
+
+        let token_bridge_state = take_state(scenario);
+
+        let verified_vaa = parse_and_verify_vaa(scenario, VAA);
+        let msg =
+            token_bridge::vaa::verify_only_once(
+                &mut token_bridge_state,
+                verified_vaa
+            );
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, caller);
+
+        let coin_meta =
+            test_scenario::take_shared<CoinMetadata<COIN_WRAPPED_12>>(scenario);
+
+        // Register the attested asset.
+        create_wrapped::complete_registration(
+            &mut token_bridge_state,
+            &mut coin_meta,
+            test_scenario::take_from_sender<
+                WrappedAssetSetup<COIN_WRAPPED_12, V__CURRENT>
+            >(
+                scenario
+            ),
+            test_scenario::take_from_sender<UpgradeCap>(scenario),
+            msg
+        );
+
+        test_scenario::return_shared(coin_meta);
+
+        // Clean up.
+        return_state(token_bridge_state);
+    }
+
+    #[test_only]
+    /// NOTE: Even though this module is `#[test_only]`, this method is tagged
+    /// with the same macro as a trick to allow another method within this
+    /// module to call `init` using OTW.
+    public fun init_register_and_mint(
+        scenario: &mut Scenario,
+        caller: address,
+        amount: u64
+    ): Balance<COIN_WRAPPED_12> {
+        use token_bridge::token_bridge_scenario::{return_state, take_state};
+
+        // First publish and register.
+        init_and_register(scenario, caller);
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, caller);
+
+        let token_bridge_state = take_state(scenario);
+        let minted =
+            wrapped_asset::mint_test_only(
+                token_registry::borrow_mut_wrapped_test_only(
+                    state::borrow_mut_token_registry_test_only(
+                        &mut token_bridge_state
+                    )
+                ),
+                amount
+            );
+
+        return_state(token_bridge_state);
+
+        minted
+    }
+}
+
+#[test_only]
+module token_bridge::coin_wrapped_12_tests {
+    use token_bridge::asset_meta::{Self};
+    use token_bridge::coin_wrapped_12::{token_meta};
+
+    #[test]
+    fun test_native_decimals() {
+        let meta = token_meta();
+        assert!(asset_meta::native_decimals(&meta) == 12, 0);
+        asset_meta::destroy(meta);
+    }
+}

+ 194 - 0
target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/test/coin_wrapped_7.move

@@ -0,0 +1,194 @@
+// SPDX-License-Identifier: Apache 2
+
+#[test_only]
+module token_bridge::coin_wrapped_7 {
+    use sui::balance::{Balance};
+    use sui::coin::{CoinMetadata, TreasuryCap};
+    use sui::package::{UpgradeCap};
+    use sui::test_scenario::{Self, Scenario};
+    use sui::transfer::{Self};
+    use sui::tx_context::{Self, TxContext};
+
+    use token_bridge::asset_meta::{Self, AssetMeta};
+    use token_bridge::create_wrapped::{Self, WrappedAssetSetup};
+    use token_bridge::state::{Self};
+    use token_bridge::token_registry::{Self};
+    use token_bridge::wrapped_asset::{Self};
+
+    use token_bridge::version_control::{V__0_2_0 as V__CURRENT};
+
+    struct COIN_WRAPPED_7 has drop {}
+
+    // TODO: need to fix the emitter address
+    // +------------------------------------------------------------------------------+
+    // | Wormhole VAA v1         | nonce: 69               | time: 0                  |
+    // | guardian set #0         | #1                      | consistency: 15          |
+    // |------------------------------------------------------------------------------|
+    // | Signature:                                                                   |
+    // |   #0: 3d8fd671611d84801dc9d14a07835e8729d217b1aac77b054175d0f91294...        |
+    // |------------------------------------------------------------------------------|
+    // | Emitter: 0x00000000000000000000000000000000deadbeef (Ethereum)               |
+    // |------------------------------------------------------------------------------|
+    // | Token attestation                                                            |
+    // | decimals: 7                                                                  |
+    // | Token: 0x00000000000000000000000000000000deafface (Ethereum)                 |
+    // | Symbol: DEC7                                                                 |
+    // | Name: DECIMALS 7                                                             |
+    // +------------------------------------------------------------------------------+
+    const VAA: vector<u8> =
+        x"010000000001003d8fd671611d84801dc9d14a07835e8729d217b1aac77b054175d0f91294040742a1ed6f3e732b2fbf208e64422816accf89dd0cd3ead20d2e0fb3d372ce221c010000000000000045000200000000000000000000000000000000000000000000000000000000deadbeef00000000000000010f0200000000000000000000000000000000000000000000000000000000deafface000207000000000000000000000000000000000000000000000000000000004445433700000000000000000000000000000000000000000000444543494d414c532037";
+
+    fun init(witness: COIN_WRAPPED_7, ctx: &mut TxContext) {
+        let (
+            setup,
+            upgrade_cap
+        ) =
+            create_wrapped::new_setup_current(
+                witness,
+                7,
+                ctx
+            );
+        transfer::public_transfer(setup, tx_context::sender(ctx));
+        transfer::public_transfer(upgrade_cap, tx_context::sender(ctx));
+    }
+
+    #[test_only]
+    public fun init_test_only(ctx: &mut TxContext) {
+        init(COIN_WRAPPED_7 {}, ctx);
+    }
+
+    public fun encoded_vaa(): vector<u8> {
+        VAA
+    }
+
+    #[allow(implicit_const_copy)]
+    public fun token_meta(): AssetMeta {
+        asset_meta::deserialize_test_only(
+            wormhole::vaa::peel_payload_from_vaa(&VAA)
+        )
+    }
+
+    #[test_only]
+    /// for a test scenario, simply deploy the coin and expose `TreasuryCap`.
+    public fun init_and_take_treasury_cap(
+        scenario: &mut Scenario,
+        caller: address
+    ): TreasuryCap<COIN_WRAPPED_7> {
+        use token_bridge::create_wrapped;
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, caller);
+
+        // Publish coin.
+        init(COIN_WRAPPED_7 {}, test_scenario::ctx(scenario));
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, caller);
+
+        create_wrapped::take_treasury_cap(
+            test_scenario::take_from_sender(scenario)
+        )
+    }
+
+    #[test_only]
+    /// For a test scenario, register this wrapped asset.
+    ///
+    /// NOTE: Even though this module is `#[test_only]`, this method is tagged
+    /// with the same macro as a trick to allow another method within this
+    /// module to call `init` using OTW.
+    public fun init_and_register(
+        scenario: &mut Scenario,
+        caller: address
+    ) {
+        use token_bridge::token_bridge_scenario::{return_state, take_state};
+        use wormhole::wormhole_scenario::{parse_and_verify_vaa};
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, caller);
+
+        // Publish coin.
+        init(COIN_WRAPPED_7 {}, test_scenario::ctx(scenario));
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, caller);
+
+        let token_bridge_state = take_state(scenario);
+
+        let verified_vaa = parse_and_verify_vaa(scenario, VAA);
+        let msg =
+            token_bridge::vaa::verify_only_once(
+                &mut token_bridge_state,
+                verified_vaa
+            );
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, caller);
+
+        let coin_meta =
+            test_scenario::take_shared<CoinMetadata<COIN_WRAPPED_7>>(scenario);
+
+        // Register the attested asset.
+        create_wrapped::complete_registration(
+            &mut token_bridge_state,
+            &mut coin_meta,
+            test_scenario::take_from_sender<
+                WrappedAssetSetup<COIN_WRAPPED_7, V__CURRENT>
+            >(
+                scenario
+            ),
+            test_scenario::take_from_sender<UpgradeCap>(scenario),
+            msg
+        );
+
+        test_scenario::return_shared(coin_meta);
+
+        // Clean up.
+        return_state(token_bridge_state);
+    }
+
+    #[test_only]
+    /// NOTE: Even though this module is `#[test_only]`, this method is tagged
+    /// with the same macro as a trick to allow another method within this
+    /// module to call `init` using OTW.
+    public fun init_register_and_mint(
+        scenario: &mut Scenario,
+        caller: address,
+        amount: u64
+    ): Balance<COIN_WRAPPED_7> {
+        use token_bridge::token_bridge_scenario::{return_state, take_state};
+
+        // First publish and register.
+        init_and_register(scenario, caller);
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, caller);
+
+        let token_bridge_state = take_state(scenario);
+        let minted =
+            wrapped_asset::mint_test_only(
+                token_registry::borrow_mut_wrapped_test_only(
+                    state::borrow_mut_token_registry_test_only(
+                        &mut token_bridge_state
+                    )
+                ),
+                amount
+            );
+
+        return_state(token_bridge_state);
+
+        minted
+    }
+}
+
+#[test_only]
+module token_bridge::coin_wrapped_7_tests {
+    use token_bridge::asset_meta::{Self};
+    use token_bridge::coin_wrapped_7::{token_meta};
+
+    #[test]
+    fun test_native_decimals() {
+        let meta = token_meta();
+        assert!(asset_meta::native_decimals(&meta) == 7, 0);
+        asset_meta::destroy(meta);
+    }
+}

+ 146 - 0
target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/test/dummy_message.move

@@ -0,0 +1,146 @@
+// SPDX-License-Identifier: Apache 2
+
+#[test_only]
+module token_bridge::dummy_message {
+    public fun encoded_transfer(): vector<u8> {
+        // let decimals = 8;
+        // let expected_amount = normalized_amount::from_raw(234567890, decimals);
+        // let expected_token_address = external_address::from_address(@0xbeef);
+        // let expected_token_chain = 1;
+        // let expected_recipient = external_address::from_address(@0xcafe);
+        // let expected_recipient_chain = 7;
+        // let expected_relayer_fee =
+        //     normalized_amount::from_raw(123456789, decimals);
+        x"01000000000000000000000000000000000000000000000000000000000dfb38d2000000000000000000000000000000000000000000000000000000000000beef0001000000000000000000000000000000000000000000000000000000000000cafe000700000000000000000000000000000000000000000000000000000000075bcd15"
+    }
+
+    public fun encoded_transfer_with_payload(): vector<u8> {
+        // let expected_amount = normalized_amount::from_raw(234567890, 8);
+        // let expected_token_address = external_address::from_address(@0xbeef);
+        // let expected_token_chain = 1;
+        // let expected_recipient = external_address::from_address(@0xcafe);
+        // let expected_recipient_chain = 7;
+        // let expected_sender = external_address::from_address(@0xdeadbeef);
+        // let expected_payload = b"All your base are belong to us.";
+        x"03000000000000000000000000000000000000000000000000000000000dfb38d2000000000000000000000000000000000000000000000000000000000000beef0001000000000000000000000000000000000000000000000000000000000000cafe0007381dd9078c322a4663c392761a0211b527c127b29583851217f948d62131f409416c6c20796f75722062617365206172652062656c6f6e6720746f2075732e"
+    }
+
+    public fun encoded_transfer_vaa_native_with_fee(): vector<u8> {
+        // emitterChain: 2,
+        // emitterAddress: '0x00000000000000000000000000000000000000000000000000000000deadbeef',
+        // amount: 3000n,
+        // tokenAddress: '0x0000000000000000000000000000000000000000000000000000000000000001',
+        // tokenChain: 21,
+        // toAddress: '0x000000000000000000000000000000000000000000000000000000000000b0b1',
+        // chain: 21,
+        // fee: 1000n
+        x"01000000000100bce07d9dce4e16f564788b0885fa31fa6c5c1bb7ee1f7d0948b8f2c2ae9e87ea4eccfc86affb8b7cf8bfcc774effe0fa7a54066d8a4310a4bb0350fd3097ab25000000000000000000000200000000000000000000000000000000000000000000000000000000deadbeef00000000000000010f010000000000000000000000000000000000000000000000000000000000000bb80bc9c77af025eb7f73940ad00c9d6f06d45253339a110b0f9ff03b822e5877d30015000000000000000000000000000000000000000000000000000000000000b0b1001500000000000000000000000000000000000000000000000000000000000003e8"
+    }
+
+    public fun encoded_transfer_with_payload_vaa_native(): vector<u8> {
+        // emitterChain: 2,
+        // emitterAddress: '0x00000000000000000000000000000000000000000000000000000000deadbeef',
+        // amount: 3000n,
+        // tokenAddress: '0x0000000000000000000000000000000000000000000000000000000000000001',
+        // tokenChain: 21,
+        // toAddress: '0x381dd9078c322a4663c392761a0211b527c127b29583851217f948d62131f409',
+        // chain: 21,
+        // fromAddress: '0x000000000000000000000000000000000000000000000000000000000badc0de',
+        // payload: 'All your base are belong to us.'
+        x"010000000001003aced6a481653aa534b2f679122e0179de056dbef47442b8c3a1a810dbdfa71049f53cab6e82362800c1558d44993fa6e958a75bd6e6a3472dd278e900041e29010000000000000000000200000000000000000000000000000000000000000000000000000000deadbeef00000000000000010f030000000000000000000000000000000000000000000000000000000000000bb80bc9c77af025eb7f73940ad00c9d6f06d45253339a110b0f9ff03b822e5877d30015381dd9078c322a4663c392761a0211b527c127b29583851217f948d62131f4090015000000000000000000000000000000000000000000000000000000000badc0de416c6c20796f75722062617365206172652062656c6f6e6720746f2075732e"
+    }
+
+    public fun encoded_transfer_vaa_wrapped_12_with_fee(): vector<u8> {
+        // emitterChain: 2,
+        // emitterAddress: '0x00000000000000000000000000000000000000000000000000000000deadbeef',
+        // amount: 3000n,
+        // tokenAddress: '0x00000000000000000000000000000000000000000000000000000000beefface',
+        // tokenChain: 2,
+        // toAddress: '0x000000000000000000000000000000000000000000000000000000000000b0b1',
+        // chain: 21,
+        // fee: 1000n
+        x"010000000001005537ca9a981a62823f57a706f3ceab648391fd99a11631296f798aa394ba6aff73540afefad8634ed573c73c5aa9a16e68906321fa6a4c8a488611b933b1f5b1000000000000000000000200000000000000000000000000000000000000000000000000000000deadbeef00000000000000010f010000000000000000000000000000000000000000000000000000000000000bb800000000000000000000000000000000000000000000000000000000beefface0002000000000000000000000000000000000000000000000000000000000000b0b1001500000000000000000000000000000000000000000000000000000000000003e8"
+    }
+
+    public fun encoded_transfer_vaa_wrapped_12_without_fee(): vector<u8> {
+        // emitterChain: 2,
+        // emitterAddress: '0x00000000000000000000000000000000000000000000000000000000deadbeef',
+        // amount: 3000n,
+        // tokenAddress: '0x00000000000000000000000000000000000000000000000000000000beefface',
+        // tokenChain: 2,
+        // toAddress: '0x000000000000000000000000000000000000000000000000000000000000b0b1',
+        // chain: 21,
+        // fee: 0n
+        x"01000000000100e5558a2955f94fdb174d7868c9f643700174949ac72b90f803bdbea00453ed4c426c055b956060c905189cb710b97916af6a77cd3168f83eca9c66b6366c85c4000000000000000000000200000000000000000000000000000000000000000000000000000000deadbeef00000000000000010f010000000000000000000000000000000000000000000000000000000000000bb800000000000000000000000000000000000000000000000000000000beefface0002000000000000000000000000000000000000000000000000000000000000b0b100150000000000000000000000000000000000000000000000000000000000000000"
+    }
+
+    public fun encoded_transfer_with_payload_wrapped_12(): vector<u8> {
+        // emitterChain: 2,
+        // emitterAddress: '0x00000000000000000000000000000000000000000000000000000000deadbeef',
+        // amount: 3000n,
+        // tokenAddress: '0x00000000000000000000000000000000000000000000000000000000beefface',
+        // tokenChain: 2,
+        // toAddress: '0x381dd9078c322a4663c392761a0211b527c127b29583851217f948d62131f409',
+        // chain: 21,
+        // fromAddress: '0x000000000000000000000000000000000000000000000000000000000badc0de',
+        // payload: 'All your base are belong to us.'
+        x"0100000000010054968c9be4059d7dc373fff8e80dfc9083c485663517534807d61d11abec64896c4185a2bdd71e3caa713d082c78f5d8b1586c56bd5042dfaba1de0ca0d978a0010000000000000000000200000000000000000000000000000000000000000000000000000000deadbeef00000000000000010f030000000000000000000000000000000000000000000000000000000000000bb800000000000000000000000000000000000000000000000000000000beefface0002381dd9078c322a4663c392761a0211b527c127b29583851217f948d62131f4090015000000000000000000000000000000000000000000000000000000000badc0de416c6c20796f75722062617365206172652062656c6f6e6720746f2075732e"
+    }
+
+    public fun encoded_transfer_vaa_wrapped_7_with_fee(): vector<u8> {
+        // emitterChain: 2,
+        // emitterAddress: '0x00000000000000000000000000000000000000000000000000000000deadbeef',
+        // amount: 3000n,
+        // tokenAddress: '0x00000000000000000000000000000000000000000000000000000000deafface',
+        // tokenChain: 2,
+        // toAddress: '0x000000000000000000000000000000000000000000000000000000000000b0b1',
+        // chain: 21,
+        // fee: 1000n
+        x"01000000000100b9dc34e110e4268ac1e0ef729513083d45b59e0c2cbee8f9fd7d7d2ed900c8ad2a5ca55310fb3741bf3ff8c611e37a2fee2852e09feb491261edf53fcc956edf010000000000000000000200000000000000000000000000000000000000000000000000000000deadbeef00000000000000010f010000000000000000000000000000000000000000000000000000000000000bb800000000000000000000000000000000000000000000000000000000deafface0002000000000000000000000000000000000000000000000000000000000000b0b1001500000000000000000000000000000000000000000000000000000000000003e8"
+    }
+
+    public fun encoded_transfer_vaa_wrapped_7_without_fee(): vector<u8> {
+        // emitterChain: 2,
+        // emitterAddress: '0x00000000000000000000000000000000000000000000000000000000deadbeef',
+        // amount: 3000n,
+        // tokenAddress: '0x00000000000000000000000000000000000000000000000000000000deafface',
+        // tokenChain: 2,
+        // toAddress: '0x000000000000000000000000000000000000000000000000000000000000b0b1',
+        // chain: 21,
+        // fee: 0n
+        x"01000000000100389f0544dc2d3f7095d4e9543ae9f6cb5c9dd6a561e95ed896c870907fe85a94373a455acac8d2ad66154df1cb19ba4ae6c583a1c2839971e6760ecaa1d9fca7000000000000000000000200000000000000000000000000000000000000000000000000000000deadbeef00000000000000010f010000000000000000000000000000000000000000000000000000000000000bb800000000000000000000000000000000000000000000000000000000deafface0002000000000000000000000000000000000000000000000000000000000000b0b100150000000000000000000000000000000000000000000000000000000000000000"
+    }
+
+    public fun encoded_transfer_vaa_wrapped_12_invalid_target_chain(): vector<u8> {
+        // emitterChain: 2,
+        // emitterAddress: '0x00000000000000000000000000000000000000000000000000000000deadbeef',
+        // amount: 3000n,
+        // tokenAddress: '0x00000000000000000000000000000000000000000000000000000000beefface',
+        // tokenChain: 2,
+        // toAddress: '0x000000000000000000000000000000000000000000000000000000000000b0b1',
+        // chain: 69,
+        // fee: 0n
+        x"010000000001009c0b89b21622bde003f8e775daffe343e65d6a537719bc977c85b0b18c26751c7bff61077e74711dfe865d935fa840a7352d7a1ccbcec4be77bfc591cd265a48000000000000000000000200000000000000000000000000000000000000000000000000000000deadbeef00000000000000010f010000000000000000000000000000000000000000000000000000000000000bb800000000000000000000000000000000000000000000000000000000beefface0002000000000000000000000000000000000000000000000000000000000000b0b1004500000000000000000000000000000000000000000000000000000000000003e8"
+    }
+
+    public fun encoded_transfer_with_payload_wrapped_12_invalid_target_chain(): vector<u8> {
+        // emitterChain: 2,
+        // emitterAddress: '0x00000000000000000000000000000000000000000000000000000000deadbeef',
+        // amount: 3000n,
+        // tokenAddress: '0x00000000000000000000000000000000000000000000000000000000beefface',
+        // tokenChain: 2,
+        // toAddress: '0x381dd9078c322a4663c392761a0211b527c127b29583851217f948d62131f409',
+        // chain: 21,
+        // fromAddress: '0x000000000000000000000000000000000000000000000000000000000badc0de',
+        // payload: 'All your base are belong to us.'
+        x"01000000000100b139a7dbb747b04509ae4f511080a9cb080e423d8db086d5c7553baed2d6151e3fbdd00e691d82662b8d1ed49ec374dba5f82e82df20921151da4b948ddce41e000000000000000000000200000000000000000000000000000000000000000000000000000000deadbeef00000000000000010f030000000000000000000000000000000000000000000000000000000000000bb800000000000000000000000000000000000000000000000000000000beefface0002381dd9078c322a4663c392761a0211b527c127b29583851217f948d62131f4090045000000000000000000000000000000000000000000000000000000000badc0de416c6c20796f75722062617365206172652062656c6f6e6720746f2075732e"
+    }
+
+    public fun encoded_register_chain_2(): vector<u8> {
+        x"0100000000010015d405c74be6d93c3c33ed6b48d8db70dfb31e0981f8098b2a6c7583083e0c3343d4a1abeb3fc1559674fa067b0c0e2e9de2fafeaecdfeae132de2c33c9d27cc0100000001000000010001000000000000000000000000000000000000000000000000000000000000000400000000016911ae00000000000000000000000000000000000000000000546f6b656e427269646765010000000200000000000000000000000000000000000000000000000000000000deadbeef"
+    }
+
+    public fun encoded_asset_meta_vaa_foreign_12(): vector<u8> {
+        x"0100000000010080366065746148420220f25a6275097370e8db40984529a6676b7a5fc9feb11755ec49ca626b858ddfde88d15601f85ab7683c5f161413b0412143241c700aff010000000100000001000200000000000000000000000000000000000000000000000000000000deadbeef000000000150eb23000200000000000000000000000000000000000000000000000000000000beefface00020c424545460000000000000000000000000000000000000000000000000000000042656566206661636520546f6b656e0000000000000000000000000000000000"
+    }
+}

+ 136 - 0
target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/test/token_bridge_scenario.move

@@ -0,0 +1,136 @@
+// SPDX-License-Identifier: Apache 2
+
+#[test_only]
+module token_bridge::token_bridge_scenario {
+    use std::vector::{Self};
+    use sui::balance::{Self};
+    use sui::package::{UpgradeCap};
+    use sui::test_scenario::{Self, Scenario};
+    use wormhole::external_address::{Self};
+    use wormhole::wormhole_scenario::{
+        deployer,
+        return_state as return_wormhole_state,
+        set_up_wormhole,
+        take_state as take_wormhole_state
+    };
+
+    use token_bridge::native_asset::{Self};
+    use token_bridge::setup::{Self, DeployerCap};
+    use token_bridge::state::{Self, State};
+    use token_bridge::token_registry::{Self};
+
+    public fun set_up_wormhole_and_token_bridge(
+        scenario: &mut Scenario,
+        wormhole_fee: u64
+    ) {
+        // init and share wormhole core bridge
+        set_up_wormhole(scenario, wormhole_fee);
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, deployer());
+
+        // Publish Token Bridge.
+        setup::init_test_only(test_scenario::ctx(scenario));
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, deployer());
+
+        let wormhole_state = take_wormhole_state(scenario);
+
+        let upgrade_cap =
+            test_scenario::take_from_sender<UpgradeCap>(scenario);
+        let emitter_cap =
+            wormhole::emitter::new(
+                &wormhole_state,
+                test_scenario::ctx(scenario)
+            );
+        let governance_chain = 1;
+        let governance_contract =
+            x"0000000000000000000000000000000000000000000000000000000000000004";
+
+        // Finally share `State`.
+        setup::complete(
+            test_scenario::take_from_sender<DeployerCap>(scenario),
+            upgrade_cap,
+            emitter_cap,
+            governance_chain,
+            governance_contract,
+            test_scenario::ctx(scenario)
+        );
+
+        // Clean up.
+        return_wormhole_state(wormhole_state);
+    }
+
+    /// Perform an upgrade (which just upticks the current version of what the
+    /// `State` believes is true).
+    public fun upgrade_token_bridge(scenario: &mut Scenario) {
+        // Clean up from activity prior.
+        test_scenario::next_tx(scenario, person());
+
+        let token_bridge_state = take_state(scenario);
+        state::test_upgrade(&mut token_bridge_state);
+
+        // Clean up.
+        return_state(token_bridge_state);
+    }
+
+    /// Register arbitrary chain ID with the same emitter address (0xdeadbeef).
+    public fun register_dummy_emitter(scenario: &mut Scenario, chain: u16) {
+        // Ignore effects.
+        test_scenario::next_tx(scenario, person());
+
+        let token_bridge_state = take_state(scenario);
+        token_bridge::register_chain::register_new_emitter_test_only(
+            &mut token_bridge_state,
+            chain,
+            external_address::from_address(@0xdeadbeef)
+        );
+
+        // Clean up.
+        return_state(token_bridge_state);
+    }
+
+    /// Register 0xdeadbeef for multiple chains.
+    public fun register_dummy_emitters(
+        scenario: &mut Scenario,
+        chains: vector<u16>
+    ) {
+        while (!vector::is_empty(&chains)) {
+            register_dummy_emitter(scenario, vector::pop_back(&mut chains));
+        };
+        vector::destroy_empty(chains);
+    }
+
+    public fun deposit_native<CoinType>(
+        token_bridge_state: &mut State,
+        deposit_amount: u64
+    ) {
+        native_asset::deposit_test_only(
+            token_registry::borrow_mut_native_test_only(
+                state::borrow_mut_token_registry_test_only(token_bridge_state)
+            ),
+            balance::create_for_testing<CoinType>(deposit_amount)
+        )
+    }
+
+    public fun person(): address {
+        wormhole::wormhole_scenario::person()
+    }
+
+    public fun two_people(): (address, address) {
+        wormhole::wormhole_scenario::two_people()
+    }
+
+    public fun three_people(): (address, address, address) {
+        wormhole::wormhole_scenario::three_people()
+    }
+
+    public fun take_state(scenario: &Scenario): State {
+        test_scenario::take_shared(scenario)
+    }
+
+    public fun return_state(token_bridge_state: State) {
+        test_scenario::return_shared(token_bridge_state);
+    }
+}

+ 1053 - 0
target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/transfer_tokens.move

@@ -0,0 +1,1053 @@
+// SPDX-License-Identifier: Apache 2
+
+/// This module implements three methods: `prepare_transfer` and
+/// `transfer_tokens`, which are meant to work together.
+///
+/// `prepare_transfer` allows a contract to pack token transfer parameters in
+/// preparation to bridge these assets to another network. Anyone can call this
+/// method to create `TransferTicket`.
+///
+/// `transfer_tokens` unpacks the `TransferTicket` and constructs a
+/// `MessageTicket`, which will be used by Wormhole's `publish_message`
+/// module.
+///
+/// The purpose of splitting this token transferring into two steps is in case
+/// Token Bridge needs to be upgraded and there is a breaking change for this
+/// module, an integrator would not be left broken. It is discouraged to put
+/// `transfer_tokens` in an integrator's package logic. Otherwise, this
+/// integrator needs to be prepared to upgrade his contract to handle the latest
+/// version of `transfer_tokens`.
+///
+/// Instead, an integrator is encouraged to execute a transaction block, which
+/// executes `transfer_tokens` using the latest Token Bridge package ID and to
+/// implement `prepare_transfer` in his contract to produce `PrepareTransfer`.
+///
+/// NOTE: Only assets that exist in the `TokenRegistry` can be bridged out,
+/// which are native Sui assets that have been attested for via `attest_token`
+/// and wrapped foreign assets that have been created using foreign asset
+/// metadata via the `create_wrapped` module.
+///
+/// See `transfer` module for serialization and deserialization of Wormhole
+/// message payload.
+module token_bridge::transfer_tokens {
+    use sui::balance::{Self, Balance};
+    use sui::coin::{Self, Coin};
+    use wormhole::bytes32::{Self};
+    use wormhole::external_address::{Self, ExternalAddress};
+    use wormhole::publish_message::{MessageTicket};
+
+    use token_bridge::native_asset::{Self};
+    use token_bridge::normalized_amount::{Self, NormalizedAmount};
+    use token_bridge::state::{Self, State, LatestOnly};
+    use token_bridge::token_registry::{Self, VerifiedAsset};
+    use token_bridge::transfer::{Self};
+    use token_bridge::wrapped_asset::{Self};
+
+    friend token_bridge::transfer_tokens_with_payload;
+
+    /// Relayer fee exceeds `Coin` object's value.
+    const E_RELAYER_FEE_EXCEEDS_AMOUNT: u64 = 0;
+
+    /// This type represents transfer data for a recipient on a foreign chain.
+    /// The only way to destroy this type is calling `transfer_tokens`.
+    ///
+    /// NOTE: An integrator that expects to bridge assets between his contracts
+    /// should probably use the `transfer_tokens_with_payload` module, which
+    /// expects a specific redeemer to complete the transfer (transfers sent
+    /// using `transfer_tokens` can be redeemed by anyone on behalf of the
+    /// encoded recipient).
+    struct TransferTicket<phantom CoinType> {
+        asset_info: VerifiedAsset<CoinType>,
+        bridged_in: Balance<CoinType>,
+        norm_amount: NormalizedAmount,
+        recipient_chain: u16,
+        recipient: vector<u8>,
+        relayer_fee: u64,
+        nonce: u32
+    }
+
+    /// `prepare_transfer` constructs token transfer parameters. Any remaining
+    /// amount (A.K.A. dust) from the funds provided will be returned along with
+    /// the `TransferTicket` type. The returned coin object is the same object
+    /// moved into this method.
+    ///
+    /// NOTE: Integrators of Token Bridge should be calling only this method
+    /// from their contracts. This method is not guarded by version control
+    /// (thus not requiring a reference to the Token Bridge `State` object), so
+    /// it is intended to work for any package version.
+    public fun prepare_transfer<CoinType>(
+        asset_info: VerifiedAsset<CoinType>,
+        funded: Coin<CoinType>,
+        recipient_chain: u16,
+        recipient: vector<u8>,
+        relayer_fee: u64,
+        nonce: u32
+    ): (
+        TransferTicket<CoinType>,
+        Coin<CoinType>
+    ) {
+        let (
+            bridged_in,
+            norm_amount
+        ) = take_truncated_amount(&asset_info, &mut funded);
+
+        let ticket =
+            TransferTicket {
+                asset_info,
+                bridged_in,
+                norm_amount,
+                relayer_fee,
+                recipient_chain,
+                recipient,
+                nonce
+            };
+
+        // The remaining amount of funded may have dust depending on the
+        // decimals of this asset.
+        (ticket, funded)
+    }
+
+    /// `transfer_tokens` is the only method that can unpack the members of
+    /// `TransferTicket`. This method takes the balance from this type and
+    /// bridges this asset out of Sui by either joining its balance in the Token
+    /// Bridge's custody for native assets or burning its balance for wrapped
+    /// assets.
+    ///
+    /// A `relayer_fee` of some value less than or equal to the bridged balance
+    /// can be specified to incentivize someone to redeem this transfer on
+    /// behalf of the `recipient`.
+    ///
+    /// This method returns the prepared Wormhole message (which should be
+    /// consumed by calling `publish_message` in a transaction block).
+    ///
+    /// NOTE: This method is guarded by a minimum build version check. This
+    /// method could break backward compatibility on an upgrade.
+    ///
+    /// It is important for integrators to refrain from calling this method
+    /// within their contracts. This method is meant to be called in a
+    /// transaction block after receiving a `TransferTicket` from calling
+    /// `prepare_transfer` within a contract. If in a circumstance where this
+    /// module has a breaking change in an upgrade, `prepare_transfer` will not
+    /// be affected by this change.
+    public fun transfer_tokens<CoinType>(
+        token_bridge_state: &mut State,
+        ticket: TransferTicket<CoinType>
+    ): MessageTicket {
+        // This capability ensures that the current build version is used.
+        let latest_only = state::assert_latest_only(token_bridge_state);
+
+        let (
+            nonce,
+            encoded_transfer
+        ) =
+            bridge_in_and_serialize_transfer(
+                &latest_only,
+                token_bridge_state,
+                ticket
+            );
+
+        // Prepare Wormhole message with encoded `Transfer`.
+        state::prepare_wormhole_message(
+            &latest_only,
+            token_bridge_state,
+            nonce,
+            encoded_transfer
+        )
+    }
+
+    /// Modify coin based on the decimals of a given coin type, which may
+    /// leave some amount if the decimals lead to truncating the coin's balance.
+    /// This method returns the extracted balance (which will be bridged out of
+    /// Sui) and the normalized amount, which will be encoded in the token
+    /// transfer payload.
+    ///
+    /// NOTE: This is a privileged method, which only this and the
+    /// `transfer_tokens_with_payload` modules can use.
+    public(friend) fun take_truncated_amount<CoinType>(
+        asset_info: &VerifiedAsset<CoinType>,
+        funded: &mut Coin<CoinType>
+    ): (
+        Balance<CoinType>,
+        NormalizedAmount
+    ) {
+        // Calculate dust. If there is any, `bridged_in` will have remaining
+        // value after split. `norm_amount` is copied since it is denormalized
+        // at this step.
+        let decimals = token_registry::coin_decimals(asset_info);
+        let norm_amount =
+            normalized_amount::from_raw(coin::value(funded), decimals);
+
+        // Split the `bridged_in` coin object to return any dust remaining on
+        // that object. Only bridge in the adjusted amount after de-normalizing
+        // the normalized amount.
+        let truncated =
+            balance::split(
+                coin::balance_mut(funded),
+                normalized_amount::to_raw(norm_amount, decimals)
+            );
+
+        (truncated, norm_amount)
+    }
+
+    /// For a given coin type, either burn Token Bridge wrapped assets or
+    /// deposit coin into Token Bridge's custody. This method returns the
+    /// canonical token info (chain ID and address), which will be encoded in
+    /// the token transfer.
+    ///
+    /// NOTE: This is a privileged method, which only this and the
+    /// `transfer_tokens_with_payload` modules can use.
+    public(friend) fun burn_or_deposit_funds<CoinType>(
+        latest_only: &LatestOnly,
+        token_bridge_state: &mut State,
+        asset_info: &VerifiedAsset<CoinType>,
+        bridged_in: Balance<CoinType>
+    ): (
+        u16,
+        ExternalAddress
+    ) {
+        // Either burn or deposit depending on `CoinType`.
+        let registry =
+            state::borrow_mut_token_registry(latest_only, token_bridge_state);
+        if (token_registry::is_wrapped(asset_info)) {
+            wrapped_asset::burn(
+                token_registry::borrow_mut_wrapped(registry),
+                bridged_in
+            );
+        } else {
+            native_asset::deposit(
+                token_registry::borrow_mut_native(registry),
+                bridged_in
+            );
+        };
+
+        // Return canonical token info.
+        (
+            token_registry::token_chain(asset_info),
+            token_registry::token_address(asset_info)
+        )
+    }
+
+    fun bridge_in_and_serialize_transfer<CoinType>(
+        latest_only: &LatestOnly,
+        token_bridge_state: &mut State,
+        ticket: TransferTicket<CoinType>
+    ): (
+        u32,
+        vector<u8>
+    ) {
+        let TransferTicket {
+            asset_info,
+            bridged_in,
+            norm_amount,
+            recipient_chain,
+            recipient,
+            relayer_fee,
+            nonce
+        } = ticket;
+
+        // Disallow `relayer_fee` to be greater than the `Coin` object's value.
+        // Keep in mind that the relayer fee is evaluated against the truncated
+        // amount.
+        let amount = sui::balance::value(&bridged_in);
+        assert!(relayer_fee <= amount, E_RELAYER_FEE_EXCEEDS_AMOUNT);
+
+        // Handle funds and get canonical token info for encoded transfer.
+        let (
+            token_chain,
+            token_address
+        ) = burn_or_deposit_funds(
+            latest_only,
+            token_bridge_state,
+            &asset_info, bridged_in
+        );
+
+        // Ensure that the recipient is a 32-byte address.
+        let recipient = external_address::new(bytes32::from_bytes(recipient));
+
+        // Finally encode `Transfer`.
+        let encoded =
+            transfer::serialize(
+                transfer::new(
+                    norm_amount,
+                    token_address,
+                    token_chain,
+                    recipient,
+                    recipient_chain,
+                    normalized_amount::from_raw(
+                        relayer_fee,
+                        token_registry::coin_decimals(&asset_info)
+                    )
+                )
+            );
+
+        (nonce, encoded)
+    }
+
+    #[test_only]
+    public fun bridge_in_and_serialize_transfer_test_only<CoinType>(
+        token_bridge_state: &mut State,
+        ticket: TransferTicket<CoinType>
+    ): (
+        u32,
+        vector<u8>
+    ) {
+        // This capability ensures that the current build version is used.
+        let latest_only = state::assert_latest_only(token_bridge_state);
+
+        bridge_in_and_serialize_transfer(
+            &latest_only,
+            token_bridge_state,
+            ticket
+        )
+    }
+}
+
+#[test_only]
+module token_bridge::transfer_token_tests {
+    use sui::coin::{Self};
+    use sui::test_scenario::{Self};
+    use wormhole::bytes32::{Self};
+    use wormhole::external_address::{Self};
+    use wormhole::publish_message::{Self};
+    use wormhole::state::{chain_id};
+
+    use token_bridge::coin_native_10::{Self, COIN_NATIVE_10};
+    use token_bridge::coin_wrapped_7::{Self, COIN_WRAPPED_7};
+    use token_bridge::native_asset::{Self};
+    use token_bridge::normalized_amount::{Self};
+    use token_bridge::state::{Self};
+    use token_bridge::token_bridge_scenario::{
+        set_up_wormhole_and_token_bridge,
+        register_dummy_emitter,
+        return_state,
+        take_state,
+        person
+    };
+    use token_bridge::token_registry::{Self};
+    use token_bridge::transfer::{Self};
+    use token_bridge::transfer_tokens::{Self};
+    use token_bridge::wrapped_asset::{Self};
+
+    /// Test consts.
+    const TEST_TARGET_RECIPIENT: vector<u8> = x"beef4269";
+    const TEST_TARGET_CHAIN: u16 = 2;
+    const TEST_NONCE: u32 = 0;
+    const TEST_COIN_NATIVE_10_DECIMALS: u8 = 10;
+    const TEST_COIN_WRAPPED_7_DECIMALS: u8 = 7;
+
+    #[test]
+    fun test_transfer_tokens_native_10() {
+        use token_bridge::transfer_tokens::{prepare_transfer, transfer_tokens};
+
+        let sender = person();
+        let my_scenario = test_scenario::begin(sender);
+        let scenario = &mut my_scenario;
+
+        // Set up contracts.
+        let wormhole_fee = 350;
+        set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
+
+        // Register foreign emitter on chain ID == 2.
+        register_dummy_emitter(scenario, TEST_TARGET_CHAIN);
+
+        // Register and mint coins.
+        let transfer_amount = 6942000;
+        let coin_10_balance =
+            coin_native_10::init_register_and_mint(
+                scenario,
+                sender,
+                transfer_amount
+            );
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, sender);
+
+        // Fetch objects necessary for sending the transfer.
+        let token_bridge_state = take_state(scenario);
+
+        // Define the relayer fee.
+        let relayer_fee = 100000;
+
+        // Balance check the Token Bridge before executing the transfer. The
+        // initial balance should be zero for COIN_NATIVE_10.
+        {
+            let registry = state::borrow_token_registry(&token_bridge_state);
+            let asset = token_registry::borrow_native<COIN_NATIVE_10>(registry);
+            assert!(native_asset::custody(asset) == 0, 0);
+        };
+
+        let asset_info = state::verified_asset(&token_bridge_state);
+        let (
+            ticket,
+            dust
+        ) =
+            prepare_transfer(
+                asset_info,
+                coin::from_balance(
+                    coin_10_balance,
+                    test_scenario::ctx(scenario)
+                ),
+                TEST_TARGET_CHAIN,
+                TEST_TARGET_RECIPIENT,
+                relayer_fee,
+                TEST_NONCE,
+            );
+        coin::destroy_zero(dust);
+
+        // Call `transfer_tokens`.
+        let prepared_msg =
+            transfer_tokens(&mut token_bridge_state, ticket);
+
+        // Balance check the Token Bridge after executing the transfer. The
+        // balance should now reflect the `transfer_amount` defined in this
+        // test.
+        {
+            let registry = state::borrow_token_registry(&token_bridge_state);
+            let asset = token_registry::borrow_native<COIN_NATIVE_10>(registry);
+            assert!(native_asset::custody(asset) == transfer_amount, 0);
+        };
+
+        // Clean up.
+        publish_message::destroy(prepared_msg);
+        return_state(token_bridge_state);
+
+        // Done.
+        test_scenario::end(my_scenario);
+    }
+
+    #[test]
+    fun test_transfer_tokens_native_10_with_dust_refund() {
+        use token_bridge::transfer_tokens::{prepare_transfer, transfer_tokens};
+
+        let sender = person();
+        let my_scenario = test_scenario::begin(sender);
+        let scenario = &mut my_scenario;
+
+        // Set up contracts.
+        let wormhole_fee = 350;
+        set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
+
+        // Register foreign emitter on chain ID == 2.
+        register_dummy_emitter(scenario, TEST_TARGET_CHAIN);
+
+        // Register and mint coins.
+        let transfer_amount = 1000069;
+        let coin_10_balance =
+            coin_native_10::init_register_and_mint(
+                scenario,
+                sender,
+                transfer_amount
+            );
+
+        // This value will be used later. The contract should return dust
+        // to the caller since COIN_NATIVE_10 has 10 decimals.
+        let expected_dust = 69;
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, sender);
+
+        // Fetch objects necessary for sending the transfer.
+        let token_bridge_state = take_state(scenario);
+
+        // Define the relayer fee.
+        let relayer_fee = 100000;
+
+        // Balance check the Token Bridge before executing the transfer. The
+        // initial balance should be zero for COIN_NATIVE_10.
+        {
+            let registry = state::borrow_token_registry(&token_bridge_state);
+            let asset = token_registry::borrow_native<COIN_NATIVE_10>(registry);
+            assert!(native_asset::custody(asset) == 0, 0);
+        };
+
+        let asset_info = state::verified_asset(&token_bridge_state);
+        let (
+            ticket,
+            dust
+        ) =
+            prepare_transfer(
+                asset_info,
+                coin::from_balance(
+                    coin_10_balance,
+                    test_scenario::ctx(scenario)
+                ),
+                TEST_TARGET_CHAIN,
+                TEST_TARGET_RECIPIENT,
+                relayer_fee,
+                TEST_NONCE,
+            );
+        assert!(coin::value(&dust) == expected_dust, 0);
+
+        // Call `transfer_tokens`.
+        let prepared_msg =
+            transfer_tokens(&mut token_bridge_state, ticket);
+
+        // Balance check the Token Bridge after executing the transfer. The
+        // balance should now reflect the `transfer_amount` less `expected_dust`
+        // defined in this test.
+        {
+            let registry = state::borrow_token_registry(&token_bridge_state);
+            let asset = token_registry::borrow_native<COIN_NATIVE_10>(registry);
+            assert!(
+                native_asset::custody(asset) == transfer_amount - expected_dust,
+                0
+            );
+        };
+
+        // Clean up.
+        publish_message::destroy(prepared_msg);
+        coin::burn_for_testing(dust);
+        return_state(token_bridge_state);
+
+        // Done.
+        test_scenario::end(my_scenario);
+    }
+
+    #[test]
+    fun test_serialize_transfer_tokens_native_10() {
+        use token_bridge::transfer_tokens::{
+            bridge_in_and_serialize_transfer_test_only,
+            prepare_transfer
+        };
+
+        let sender = person();
+        let my_scenario = test_scenario::begin(sender);
+        let scenario = &mut my_scenario;
+
+        // Set up contracts.
+        let wormhole_fee = 350;
+        set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
+
+        // Register foreign emitter on chain ID == 2.
+        register_dummy_emitter(scenario, TEST_TARGET_CHAIN);
+
+        // Register and mint coins.
+        let transfer_amount = 6942000;
+        let bridged_coin_10 =
+            coin::from_balance(
+                coin_native_10::init_register_and_mint(
+                    scenario,
+                    sender,
+                    transfer_amount
+                ),
+                test_scenario::ctx(scenario)
+            );
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, sender);
+
+        // Fetch objects necessary for sending the transfer.
+        let token_bridge_state = take_state(scenario);
+
+        // Define the relayer fee.
+        let relayer_fee = 100000;
+
+        let asset_info = state::verified_asset(&token_bridge_state);
+        let expected_token_address = token_registry::token_address(&asset_info);
+
+        let (
+            ticket,
+            dust
+        ) =
+            prepare_transfer(
+                asset_info,
+                bridged_coin_10,
+                TEST_TARGET_CHAIN,
+                TEST_TARGET_RECIPIENT,
+                relayer_fee,
+                TEST_NONCE,
+            );
+        coin::destroy_zero(dust);
+
+        // Call `transfer_tokens`.
+        let (
+            nonce,
+            payload
+        ) =
+            bridge_in_and_serialize_transfer_test_only(
+                &mut token_bridge_state,
+                ticket
+            );
+        assert!(nonce == TEST_NONCE, 0);
+
+        // Construct expected payload from scratch and confirm that the
+        // `transfer_tokens` call produces the same payload.
+        let expected_amount =
+            normalized_amount::from_raw(
+                transfer_amount,
+                TEST_COIN_NATIVE_10_DECIMALS
+            );
+        let expected_relayer_fee =
+            normalized_amount::from_raw(
+                relayer_fee,
+                TEST_COIN_NATIVE_10_DECIMALS
+            );
+
+        let expected_payload =
+            transfer::new_test_only(
+                expected_amount,
+                expected_token_address,
+                chain_id(),
+                external_address::new(
+                    bytes32::from_bytes(TEST_TARGET_RECIPIENT)
+                ),
+                TEST_TARGET_CHAIN,
+                expected_relayer_fee
+            );
+        assert!(transfer::serialize_test_only(expected_payload) == payload, 0);
+
+        // Clean up.
+        return_state(token_bridge_state);
+
+        // Done.
+        test_scenario::end(my_scenario);
+    }
+
+    #[test]
+    fun test_transfer_tokens_wrapped_7() {
+        use token_bridge::transfer_tokens::{prepare_transfer, transfer_tokens};
+
+        let sender = person();
+        let my_scenario = test_scenario::begin(sender);
+        let scenario = &mut my_scenario;
+
+        // Set up contracts.
+        let wormhole_fee = 350;
+        set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
+
+        // Register foreign emitter on chain ID == 2.
+        register_dummy_emitter(scenario, TEST_TARGET_CHAIN);
+
+        // Register and mint coins.
+        let transfer_amount = 42069000;
+        let coin_7_balance =
+            coin_wrapped_7::init_register_and_mint(
+                scenario,
+                sender,
+                transfer_amount
+            );
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, sender);
+
+        // Fetch objects necessary for sending the transfer.
+        let token_bridge_state = take_state(scenario);
+
+        // Define the relayer fee.
+        let relayer_fee = 100000;
+
+        // Balance check the Token Bridge before executing the transfer. The
+        // initial balance should be the `transfer_amount` for COIN_WRAPPED_7.
+        {
+            let registry = state::borrow_token_registry(&token_bridge_state);
+            let asset =
+                token_registry::borrow_wrapped<COIN_WRAPPED_7>(registry);
+            assert!(wrapped_asset::total_supply(asset) == transfer_amount, 0);
+        };
+
+        let asset_info = state::verified_asset(&token_bridge_state);
+        let (
+            ticket,
+            dust
+        ) =
+            prepare_transfer(
+                asset_info,
+                coin::from_balance(
+                    coin_7_balance,
+                    test_scenario::ctx(scenario)
+                ),
+                TEST_TARGET_CHAIN,
+                TEST_TARGET_RECIPIENT,
+                relayer_fee,
+                TEST_NONCE,
+            );
+        coin::destroy_zero(dust);
+
+        // Call `transfer_tokens`.
+        let prepared_msg =
+            transfer_tokens(&mut token_bridge_state, ticket);
+
+        // Balance check the Token Bridge after executing the transfer. The
+        // balance should be zero, since tokens are burned when an outbound
+        // wrapped token transfer occurs.
+        {
+            let registry = state::borrow_token_registry(&token_bridge_state);
+            let asset = token_registry::borrow_wrapped<COIN_WRAPPED_7>(registry);
+            assert!(wrapped_asset::total_supply(asset) == 0, 0);
+        };
+
+        // Clean up.
+        publish_message::destroy(prepared_msg);
+        return_state(token_bridge_state);
+
+        // Done.
+        test_scenario::end(my_scenario);
+    }
+
+    #[test]
+    fun test_serialize_transfer_tokens_wrapped_7() {
+        use token_bridge::transfer_tokens::{
+            bridge_in_and_serialize_transfer_test_only,
+            prepare_transfer
+        };
+
+        let sender = person();
+        let my_scenario = test_scenario::begin(sender);
+        let scenario = &mut my_scenario;
+
+        // Set up contracts.
+        let wormhole_fee = 350;
+        set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
+
+        // Register foreign emitter on chain ID == 2.
+        register_dummy_emitter(scenario, TEST_TARGET_CHAIN);
+
+        // Register and mint coins.
+        let transfer_amount = 6942000;
+        let bridged_coin_7 =
+            coin::from_balance(
+                coin_wrapped_7::init_register_and_mint(
+                    scenario,
+                    sender,
+                    transfer_amount
+                ),
+                test_scenario::ctx(scenario)
+            );
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, sender);
+
+        // Fetch objects necessary for sending the transfer.
+        let token_bridge_state = take_state(scenario);
+
+        // Define the relayer fee.
+        let relayer_fee = 100000;
+
+        let asset_info = state::verified_asset(&token_bridge_state);
+        let expected_token_address = token_registry::token_address(&asset_info);
+        let expected_token_chain = token_registry::token_chain(&asset_info);
+
+        let (
+            ticket,
+            dust
+        ) =
+            prepare_transfer(
+                asset_info,
+                bridged_coin_7,
+                TEST_TARGET_CHAIN,
+                TEST_TARGET_RECIPIENT,
+                relayer_fee,
+                TEST_NONCE,
+            );
+        coin::destroy_zero(dust);
+
+        // Call `transfer_tokens`.
+        let (
+            nonce,
+            payload
+        ) =
+            bridge_in_and_serialize_transfer_test_only(
+                &mut token_bridge_state,
+                ticket
+            );
+        assert!(nonce == TEST_NONCE, 0);
+
+        // Construct expected payload from scratch and confirm that the
+        // `transfer_tokens` call produces the same payload.
+        let expected_amount =
+            normalized_amount::from_raw(
+                transfer_amount,
+                TEST_COIN_WRAPPED_7_DECIMALS
+            );
+        let expected_relayer_fee =
+            normalized_amount::from_raw(
+                relayer_fee,
+                TEST_COIN_WRAPPED_7_DECIMALS
+            );
+
+        let expected_payload =
+            transfer::new_test_only(
+                expected_amount,
+                expected_token_address,
+                expected_token_chain,
+                external_address::new(
+                    bytes32::from_bytes(TEST_TARGET_RECIPIENT)
+                ),
+                TEST_TARGET_CHAIN,
+                expected_relayer_fee
+            );
+        assert!(transfer::serialize_test_only(expected_payload) == payload, 0);
+
+        // Clean up.
+        return_state(token_bridge_state);
+
+        // Done.
+        test_scenario::end(my_scenario);
+    }
+
+    #[test]
+    #[expected_failure(abort_code = token_registry::E_UNREGISTERED)]
+    fun test_cannot_transfer_tokens_native_not_registered() {
+        use token_bridge::transfer_tokens::{prepare_transfer, transfer_tokens};
+
+        let sender = person();
+        let my_scenario = test_scenario::begin(sender);
+        let scenario = &mut my_scenario;
+
+        // Set up contracts.
+        let wormhole_fee = 350;
+        set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
+
+        // Register foreign emitter on chain ID == 2.
+        register_dummy_emitter(scenario, TEST_TARGET_CHAIN);
+
+        // Initialize COIN_NATIVE_10 (but don't register it).
+        coin_native_10::init_test_only(test_scenario::ctx(scenario));
+
+        // NOTE: This test purposely doesn't `attest` COIN_NATIVE_10.
+        let transfer_amount = 6942000;
+        let test_coins =
+            coin::mint_for_testing<COIN_NATIVE_10>(
+                transfer_amount,
+                test_scenario::ctx(scenario)
+            );
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, sender);
+
+        // Fetch objects necessary for sending the transfer.
+        let token_bridge_state = take_state(scenario);
+
+        // Define the relayer fee.
+        let relayer_fee = 100000;
+
+        let asset_info = state::verified_asset(&token_bridge_state);
+        let (
+            ticket,
+            dust
+        ) =
+            prepare_transfer(
+                asset_info,
+                test_coins,
+                TEST_TARGET_CHAIN,
+                TEST_TARGET_RECIPIENT,
+                relayer_fee,
+                TEST_NONCE,
+            );
+        coin::destroy_zero(dust);
+
+        // You shall not pass!
+        let prepared_msg =
+            transfer_tokens(&mut token_bridge_state, ticket);
+
+        // Clean up.
+        publish_message::destroy(prepared_msg);
+
+        abort 42
+    }
+
+    #[test]
+    #[expected_failure(abort_code = token_registry::E_UNREGISTERED)]
+    fun test_cannot_transfer_tokens_wrapped_not_registered() {
+        use token_bridge::transfer_tokens::{prepare_transfer, transfer_tokens};
+
+        let sender = person();
+        let my_scenario = test_scenario::begin(sender);
+        let scenario = &mut my_scenario;
+
+        // Set up contracts.
+        let wormhole_fee = 350;
+        set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
+
+        // Register foreign emitter on chain ID == 2.
+        register_dummy_emitter(scenario, TEST_TARGET_CHAIN);
+
+        // Initialize COIN_WRAPPED_7 (but don't register it).
+        coin_native_10::init_test_only(test_scenario::ctx(scenario));
+
+        let treasury_cap =
+            coin_wrapped_7::init_and_take_treasury_cap(
+                scenario,
+                sender
+            );
+        sui::test_utils::destroy(treasury_cap);
+
+        // NOTE: This test purposely doesn't `attest` COIN_WRAPPED_7.
+        let transfer_amount = 42069;
+        let test_coins =
+            coin::mint_for_testing<COIN_WRAPPED_7>(
+                transfer_amount,
+                test_scenario::ctx(scenario)
+            );
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, sender);
+
+        // Fetch objects necessary for sending the transfer.
+        let token_bridge_state = take_state(scenario);
+
+        // Define the relayer fee.
+        let relayer_fee = 1000;
+
+        let asset_info = state::verified_asset(&token_bridge_state);
+        let (
+            ticket,
+            dust
+        ) =
+            prepare_transfer(
+                asset_info,
+                test_coins,
+                TEST_TARGET_CHAIN,
+                TEST_TARGET_RECIPIENT,
+                relayer_fee,
+                TEST_NONCE,
+            );
+        coin::destroy_zero(dust);
+
+        // You shall not pass!
+        let prepared_msg =
+            transfer_tokens(&mut token_bridge_state, ticket);
+
+        // Clean up.
+        publish_message::destroy(prepared_msg);
+
+        abort 42
+    }
+
+    #[test]
+    #[expected_failure(
+        abort_code = transfer_tokens::E_RELAYER_FEE_EXCEEDS_AMOUNT
+    )]
+    fun test_cannot_transfer_tokens_fee_exceeds_amount() {
+        use token_bridge::transfer_tokens::{prepare_transfer, transfer_tokens};
+
+        let sender = person();
+        let my_scenario = test_scenario::begin(sender);
+        let scenario = &mut my_scenario;
+
+        // Set up contracts.
+        let wormhole_fee = 350;
+        set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
+
+        // Register foreign emitter on chain ID == 2.
+        register_dummy_emitter(scenario, TEST_TARGET_CHAIN);
+
+        // NOTE: The `relayer_fee` is intentionally set to a higher number
+        // than the `transfer_amount`.
+        let relayer_fee = 100001;
+        let transfer_amount = 100000;
+        let coin_10_balance =
+            coin_native_10::init_register_and_mint(
+                scenario,
+                sender,
+                transfer_amount
+            );
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, sender);
+
+        // Fetch objects necessary for sending the transfer.
+        let token_bridge_state = take_state(scenario);
+
+        let asset_info = state::verified_asset(&token_bridge_state);
+        let (
+            ticket,
+            dust
+        ) =
+            prepare_transfer(
+                asset_info,
+                coin::from_balance(
+                    coin_10_balance,
+                    test_scenario::ctx(scenario)
+                ),
+                TEST_TARGET_CHAIN,
+                TEST_TARGET_RECIPIENT,
+                relayer_fee,
+                TEST_NONCE,
+            );
+        coin::destroy_zero(dust);
+
+        // You shall not pass!
+        let prepared_msg =
+            transfer_tokens(&mut token_bridge_state, ticket);
+
+        // Done.
+        publish_message::destroy(prepared_msg);
+
+        abort 42
+    }
+
+    #[test]
+    #[expected_failure(abort_code = wormhole::package_utils::E_NOT_CURRENT_VERSION)]
+    fun test_cannot_transfer_tokens_outdated_version() {
+        use token_bridge::transfer_tokens::{prepare_transfer, transfer_tokens};
+
+        let sender = person();
+        let my_scenario = test_scenario::begin(sender);
+        let scenario = &mut my_scenario;
+
+        // Set up contracts.
+        let wormhole_fee = 350;
+        set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
+
+        // Register foreign emitter on chain ID == 2.
+        register_dummy_emitter(scenario, TEST_TARGET_CHAIN);
+
+        // Register and mint coins.
+        let transfer_amount = 6942000;
+        let coin_10_balance =
+            coin_native_10::init_register_and_mint(
+                scenario,
+                sender,
+                transfer_amount
+            );
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, sender);
+
+        // Fetch objects necessary for sending the transfer.
+        let token_bridge_state = take_state(scenario);
+
+        let asset_info = state::verified_asset(&token_bridge_state);
+
+        let relayer_fee = 0;
+
+        let (
+            ticket,
+            dust
+        ) =
+            prepare_transfer(
+                asset_info,
+                coin::from_balance(
+                    coin_10_balance,
+                    test_scenario::ctx(scenario)
+                ),
+                TEST_TARGET_CHAIN,
+                TEST_TARGET_RECIPIENT,
+                relayer_fee,
+                TEST_NONCE,
+            );
+        coin::destroy_zero(dust);
+
+        // Conveniently roll version back.
+        state::reverse_migrate_version(&mut token_bridge_state);
+
+        // Simulate executing with an outdated build by upticking the minimum
+        // required version for `publish_message` to something greater than
+        // this build.
+        state::migrate_version_test_only(
+            &mut token_bridge_state,
+            token_bridge::version_control::previous_version_test_only(),
+            token_bridge::version_control::next_version()
+        );
+
+        // You shall not pass!
+        let prepared_msg =
+            transfer_tokens(&mut token_bridge_state, ticket);
+
+        // Clean up.
+        publish_message::destroy(prepared_msg);
+
+        abort 42
+    }
+}

+ 812 - 0
target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/transfer_tokens_with_payload.move

@@ -0,0 +1,812 @@
+// SPDX-License-Identifier: Apache 2
+
+/// This module implements three methods: `prepare_transfer` and
+/// `transfer_tokens_with_payload`, which are meant to work together.
+///
+/// `prepare_transfer` allows a contract to pack token transfer parameters with
+/// an arbitrary payload in preparation to bridge these assets to another
+/// network. Only an `EmitterCap` has the capability to create
+/// `TransferTicket`. The `EmitterCap` object ID is encoded as the
+/// sender.
+///
+/// `transfer_tokens_with_payload` unpacks the `TransferTicket` and
+/// constructs a `MessageTicket`, which will be used by Wormhole's
+/// `publish_message` module.
+///
+/// The purpose of splitting this token transferring into two steps is in case
+/// Token Bridge needs to be upgraded and there is a breaking change for this
+/// module, an integrator would not be left broken. It is discouraged to put
+/// `transfer_tokens_with_payload` in an integrator's package logic. Otherwise,
+/// this integrator needs to be prepared to upgrade his contract to handle the
+/// latest version of `transfer_tokens_with_payload`.
+///
+/// Instead, an integrator is encouraged to execute a transaction block, which
+/// executes `transfer_tokens_with_payload` using the latest Token Bridge
+/// package ID and to implement `prepare_transfer` in his contract to produce
+/// `PrepareTransferWithPayload`.
+///
+/// NOTE: Only assets that exist in the `TokenRegistry` can be bridged out,
+/// which are native Sui assets that have been attested for via `attest_token`
+/// and wrapped foreign assets that have been created using foreign asset
+/// metadata via the `create_wrapped` module.
+///
+/// See `transfer_with_payload` module for serialization and deserialization of
+/// Wormhole message payload.
+module token_bridge::transfer_tokens_with_payload {
+    use sui::balance::{Balance};
+    use sui::coin::{Coin};
+    use sui::object::{Self, ID};
+    use wormhole::bytes32::{Self};
+    use wormhole::emitter::{EmitterCap};
+    use wormhole::external_address::{Self};
+    use wormhole::publish_message::{MessageTicket};
+
+    use token_bridge::normalized_amount::{NormalizedAmount};
+    use token_bridge::state::{Self, State, LatestOnly};
+    use token_bridge::token_registry::{VerifiedAsset};
+    use token_bridge::transfer_with_payload::{Self};
+
+    /// This type represents transfer data for a specific redeemer contract on a
+    /// foreign chain. The only way to destroy this type is calling
+    /// `transfer_tokens_with_payload`. Only the owner of an `EmitterCap` has
+    /// the capability of generating `TransferTicket`. This emitter
+    /// cap will usually live in an integrator's contract storage object.
+    struct TransferTicket<phantom CoinType> {
+        asset_info: VerifiedAsset<CoinType>,
+        bridged_in: Balance<CoinType>,
+        norm_amount: NormalizedAmount,
+        sender: ID,
+        redeemer_chain: u16,
+        redeemer: vector<u8>,
+        payload: vector<u8>,
+        nonce: u32
+    }
+
+    /// `prepare_transfer` constructs token transfer parameters. Any remaining
+    /// amount (A.K.A. dust) from the funds provided will be returned along with
+    /// the `TransferTicket` type. The returned coin object is the
+    /// same object moved into this method.
+    ///
+    /// NOTE: Integrators of Token Bridge should be calling only this method
+    /// from their contracts. This method is not guarded by version control
+    /// (thus not requiring a reference to the Token Bridge `State` object), so
+    /// it is intended to work for any package version.
+    public fun prepare_transfer<CoinType>(
+        emitter_cap: &EmitterCap,
+        asset_info: VerifiedAsset<CoinType>,
+        funded: Coin<CoinType>,
+        redeemer_chain: u16,
+        redeemer: vector<u8>,
+        payload: vector<u8>,
+        nonce: u32
+    ): (
+        TransferTicket<CoinType>,
+        Coin<CoinType>
+    ) {
+        use token_bridge::transfer_tokens::{take_truncated_amount};
+
+        let (
+            bridged_in,
+            norm_amount
+        ) = take_truncated_amount(&asset_info, &mut funded);
+
+        let prepared_transfer =
+            TransferTicket {
+                asset_info,
+                bridged_in,
+                norm_amount,
+                sender: object::id(emitter_cap),
+                redeemer_chain,
+                redeemer,
+                payload,
+                nonce
+            };
+
+        // The remaining amount of funded may have dust depending on the
+        // decimals of this asset.
+        (prepared_transfer, funded)
+    }
+
+    /// `transfer_tokens_with_payload` is the only method that can unpack the
+    /// members of `TransferTicket`. This method takes the balance
+    /// from this type and bridges this asset out of Sui by either joining its
+    /// balance in the Token Bridge's custody for native assets or burning its
+    /// balance for wrapped assets.
+    ///
+    /// The unpacked sender ID comes from an `EmitterCap`. It is encoded as the
+    /// sender of these assets. And associated with this transfer is an
+    /// arbitrary payload, which can be consumed by the specified redeemer and
+    /// used as instructions for a contract composing with Token Bridge.
+    ///
+    /// This method returns the prepared Wormhole message (which should be
+    /// consumed by calling `publish_message` in a transaction block).
+    ///
+    /// NOTE: This method is guarded by a minimum build version check. This
+    /// method could break backward compatibility on an upgrade.
+    ///
+    /// It is important for integrators to refrain from calling this method
+    /// within their contracts. This method is meant to be called in a
+    /// transaction block after receiving a `TransferTicket` from calling
+    /// `prepare_transfer` within a contract. If in a circumstance where this
+    /// module has a breaking change in an upgrade, `prepare_transfer` will not
+    /// be affected by this change.
+    public fun transfer_tokens_with_payload<CoinType>(
+        token_bridge_state: &mut State,
+        prepared_transfer: TransferTicket<CoinType>
+    ): MessageTicket {
+        // This capability ensures that the current build version is used.
+        let latest_only = state::assert_latest_only(token_bridge_state);
+
+        // Encode Wormhole message payload.
+        let (
+            nonce,
+            encoded_transfer_with_payload
+         ) =
+            bridge_in_and_serialize_transfer(
+                &latest_only,
+                token_bridge_state,
+                prepared_transfer
+            );
+
+        // Prepare Wormhole message with encoded `TransferWithPayload`.
+        state::prepare_wormhole_message(
+            &latest_only,
+            token_bridge_state,
+            nonce,
+            encoded_transfer_with_payload
+        )
+    }
+
+    fun bridge_in_and_serialize_transfer<CoinType>(
+        latest_only: &LatestOnly,
+        token_bridge_state: &mut State,
+        prepared_transfer: TransferTicket<CoinType>
+    ): (
+        u32,
+        vector<u8>
+    ) {
+        use token_bridge::transfer_tokens::{burn_or_deposit_funds};
+
+        let TransferTicket {
+            asset_info,
+            bridged_in,
+            norm_amount,
+            sender,
+            redeemer_chain,
+            redeemer,
+            payload,
+            nonce
+        } = prepared_transfer;
+
+        let (
+            token_chain,
+            token_address
+        ) =
+            burn_or_deposit_funds(
+                latest_only,
+                token_bridge_state,
+                &asset_info,
+                bridged_in
+            );
+
+        let redeemer = external_address::new(bytes32::from_bytes(redeemer));
+
+        let encoded =
+            transfer_with_payload::serialize(
+                transfer_with_payload::new(
+                    sender,
+                    norm_amount,
+                    token_address,
+                    token_chain,
+                    redeemer,
+                    redeemer_chain,
+                    payload
+                )
+            );
+
+        (nonce, encoded)
+    }
+
+    #[test_only]
+    public fun bridge_in_and_serialize_transfer_test_only<CoinType>(
+        token_bridge_state: &mut State,
+        prepared_transfer: TransferTicket<CoinType>
+    ): (
+        u32,
+        vector<u8>
+    ) {
+        // This capability ensures that the current build version is used.
+        let latest_only = state::assert_latest_only(token_bridge_state);
+
+        bridge_in_and_serialize_transfer(
+            &latest_only,
+            token_bridge_state,
+            prepared_transfer
+        )
+    }
+}
+
+#[test_only]
+module token_bridge::transfer_tokens_with_payload_tests {
+    use sui::coin::{Self};
+    use sui::object::{Self};
+    use sui::test_scenario::{Self};
+    use wormhole::bytes32::{Self};
+    use wormhole::emitter::{Self};
+    use wormhole::external_address::{Self};
+    use wormhole::publish_message::{Self};
+    use wormhole::state::{chain_id};
+
+    use token_bridge::coin_wrapped_7::{Self, COIN_WRAPPED_7};
+    use token_bridge::coin_native_10::{Self, COIN_NATIVE_10};
+    use token_bridge::native_asset::{Self};
+    use token_bridge::normalized_amount::{Self};
+    use token_bridge::state::{Self};
+    use token_bridge::token_bridge_scenario::{
+        set_up_wormhole_and_token_bridge,
+        register_dummy_emitter,
+        return_state,
+        take_state,
+        person
+    };
+    use token_bridge::token_registry::{Self};
+    use token_bridge::transfer_with_payload::{Self};
+    use token_bridge::wrapped_asset::{Self};
+
+    /// Test consts.
+    const TEST_TARGET_RECIPIENT: vector<u8> = x"beef4269";
+    const TEST_TARGET_CHAIN: u16 = 2;
+    const TEST_NONCE: u32 = 0;
+    const TEST_COIN_NATIVE_10_DECIMALS: u8 = 10;
+    const TEST_COIN_WRAPPED_7_DECIMALS: u8 = 7;
+    const TEST_MESSAGE_PAYLOAD: vector<u8> = x"deadbeefdeadbeef";
+
+    #[test]
+    fun test_transfer_tokens_with_payload_native_10() {
+        use token_bridge::transfer_tokens_with_payload::{
+            prepare_transfer,
+            transfer_tokens_with_payload
+        };
+
+        let sender = person();
+        let my_scenario = test_scenario::begin(sender);
+        let scenario = &mut my_scenario;
+
+        // Set up contracts.
+        let wormhole_fee = 350;
+        set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
+
+        // Register foreign emitter on chain ID == 2.
+        register_dummy_emitter(scenario, TEST_TARGET_CHAIN);
+
+        // Register and mint coins.
+        let transfer_amount = 6942000;
+        let coin_10_balance =
+            coin_native_10::init_register_and_mint(
+                scenario,
+                sender,
+                transfer_amount
+            );
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, sender);
+
+        // Fetch objects necessary for sending the transfer.
+        let token_bridge_state = take_state(scenario);
+
+        // Balance check the Token Bridge before executing the transfer. The
+        // initial balance should be zero for COIN_NATIVE_10.
+        {
+            let registry = state::borrow_token_registry(&token_bridge_state);
+            let asset = token_registry::borrow_native<COIN_NATIVE_10>(registry);
+            assert!(native_asset::custody(asset) == 0, 0);
+        };
+
+        // Register and obtain a new wormhole emitter cap.
+        let emitter_cap = emitter::dummy();
+
+        let asset_info = state::verified_asset(&token_bridge_state);
+        let (
+            prepared_transfer,
+            dust
+        ) =
+            prepare_transfer(
+                &emitter_cap,
+                asset_info,
+                coin::from_balance(
+                    coin_10_balance,
+                    test_scenario::ctx(scenario)
+                ),
+                TEST_TARGET_CHAIN,
+                TEST_TARGET_RECIPIENT,
+                TEST_MESSAGE_PAYLOAD,
+                TEST_NONCE,
+            );
+        coin::destroy_zero(dust);
+
+        // Call `transfer_tokens_with_payload`.
+        let prepared_msg =
+            transfer_tokens_with_payload(
+                &mut token_bridge_state,
+                prepared_transfer
+            );
+
+        // Balance check the Token Bridge after executing the transfer. The
+        // balance should now reflect the `transfer_amount` defined in this
+        // test.
+        {
+            let registry = state::borrow_token_registry(&token_bridge_state);
+            let asset = token_registry::borrow_native<COIN_NATIVE_10>(registry);
+            assert!(native_asset::custody(asset) == transfer_amount, 0);
+        };
+
+        // Clean up.
+        publish_message::destroy(prepared_msg);
+        return_state(token_bridge_state);
+        emitter::destroy_test_only(emitter_cap);
+
+        // Done.
+        test_scenario::end(my_scenario);
+    }
+
+    #[test]
+    fun test_transfer_tokens_native_10_with_dust_refund() {
+        use token_bridge::transfer_tokens_with_payload::{
+            prepare_transfer,
+            transfer_tokens_with_payload
+        };
+
+        let sender = person();
+        let my_scenario = test_scenario::begin(sender);
+        let scenario = &mut my_scenario;
+
+        // Set up contracts.
+        let wormhole_fee = 350;
+        set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
+
+        // Register foreign emitter on chain ID == 2.
+        register_dummy_emitter(scenario, TEST_TARGET_CHAIN);
+
+        // Register and mint coins.
+        let transfer_amount = 1000069;
+        let coin_10_balance = coin_native_10::init_register_and_mint(
+            scenario,
+            sender,
+            transfer_amount
+        );
+
+        // This value will be used later. The contract should return dust
+        // to the caller since COIN_NATIVE_10 has 10 decimals.
+        let expected_dust = 69;
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, sender);
+
+        // Fetch objects necessary for sending the transfer.
+        let token_bridge_state = take_state(scenario);
+
+        // Balance check the Token Bridge before executing the transfer. The
+        // initial balance should be zero for COIN_NATIVE_10.
+        {
+            let registry = state::borrow_token_registry(&token_bridge_state);
+            let asset = token_registry::borrow_native<COIN_NATIVE_10>(registry);
+            assert!(native_asset::custody(asset) == 0, 0);
+        };
+
+        // Register and obtain a new wormhole emitter cap.
+        let emitter_cap = emitter::dummy();
+
+        let asset_info = state::verified_asset(&token_bridge_state);
+        let (
+            prepared_transfer,
+            dust
+        ) =
+            prepare_transfer(
+                &emitter_cap,
+                asset_info,
+                coin::from_balance(
+                    coin_10_balance,
+                    test_scenario::ctx(scenario)
+                ),
+                TEST_TARGET_CHAIN,
+                TEST_TARGET_RECIPIENT,
+                TEST_MESSAGE_PAYLOAD,
+                TEST_NONCE,
+            );
+        assert!(coin::value(&dust) == expected_dust, 0);
+
+        // Call `transfer_tokens`.
+        let prepared_msg =
+            transfer_tokens_with_payload(
+                &mut token_bridge_state,
+                prepared_transfer
+            );
+
+        // Balance check the Token Bridge after executing the transfer. The
+        // balance should now reflect the `transfer_amount` less `expected_dust`
+        // defined in this test.
+        {
+            let registry = state::borrow_token_registry(&token_bridge_state);
+            let asset = token_registry::borrow_native<COIN_NATIVE_10>(registry);
+            assert!(
+                native_asset::custody(asset) == transfer_amount - expected_dust,
+                0
+            );
+        };
+
+        // Clean up.
+        publish_message::destroy(prepared_msg);
+        coin::burn_for_testing(dust);
+        emitter::destroy_test_only(emitter_cap);
+        return_state(token_bridge_state);
+
+        // Done.
+        test_scenario::end(my_scenario);
+    }
+
+    #[test]
+    fun test_serialize_transfer_tokens_native_10() {
+        use token_bridge::transfer_tokens_with_payload::{
+            bridge_in_and_serialize_transfer_test_only,
+            prepare_transfer
+        };
+
+        let sender = person();
+        let my_scenario = test_scenario::begin(sender);
+        let scenario = &mut my_scenario;
+
+        // Set up contracts.
+        let wormhole_fee = 350;
+        set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
+
+        // Register foreign emitter on chain ID == 2.
+        register_dummy_emitter(scenario, TEST_TARGET_CHAIN);
+
+        // Register and mint coins.
+        let transfer_amount = 6942000;
+        let bridge_coin_10 =
+            coin::from_balance(
+                coin_native_10::init_register_and_mint(
+                    scenario,
+                    sender,
+                    transfer_amount
+                ),
+                test_scenario::ctx(scenario)
+            );
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, sender);
+
+        // Fetch objects necessary for sending the transfer.
+        let token_bridge_state = take_state(scenario);
+
+        // Register and obtain a new wormhole emitter cap.
+        let emitter_cap = emitter::dummy();
+
+        let asset_info = state::verified_asset(&token_bridge_state);
+        let expected_token_address = token_registry::token_address(&asset_info);
+
+        let (
+            prepared_transfer,
+            dust
+        ) =
+            prepare_transfer(
+                &emitter_cap,
+                asset_info,
+                bridge_coin_10,
+                TEST_TARGET_CHAIN,
+                TEST_TARGET_RECIPIENT,
+                TEST_MESSAGE_PAYLOAD,
+                TEST_NONCE,
+            );
+        coin::destroy_zero(dust);
+
+        // Serialize the payload.
+        let (
+            nonce,
+            payload
+         ) =
+            bridge_in_and_serialize_transfer_test_only(
+                &mut token_bridge_state,
+                prepared_transfer
+            );
+        assert!(nonce == TEST_NONCE, 0);
+
+        // Construct expected payload from scratch and confirm that the
+        // `transfer_tokens` call produces the same payload.
+        let expected_amount = normalized_amount::from_raw(
+            transfer_amount,
+            TEST_COIN_NATIVE_10_DECIMALS
+        );
+
+        let expected_payload =
+            transfer_with_payload::new_test_only(
+                object::id(&emitter_cap),
+                expected_amount,
+                expected_token_address,
+                chain_id(),
+                external_address::new(bytes32::from_bytes(TEST_TARGET_RECIPIENT)),
+                TEST_TARGET_CHAIN,
+                TEST_MESSAGE_PAYLOAD
+            );
+        assert!(
+            transfer_with_payload::serialize(expected_payload) == payload,
+            0
+        );
+
+        // Clean up.
+        return_state(token_bridge_state);
+        emitter::destroy_test_only(emitter_cap);
+
+        // Done.
+        test_scenario::end(my_scenario);
+    }
+
+    #[test]
+    fun test_transfer_tokens_with_payload_wrapped_7() {
+        use token_bridge::transfer_tokens_with_payload::{
+            prepare_transfer,
+            transfer_tokens_with_payload
+        };
+
+        let sender = person();
+        let my_scenario = test_scenario::begin(sender);
+        let scenario = &mut my_scenario;
+
+        // Set up contracts.
+        let wormhole_fee = 350;
+        set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
+
+        // Register foreign emitter on chain ID == 2.
+        register_dummy_emitter(scenario, TEST_TARGET_CHAIN);
+
+        // Register and mint coins.
+        let transfer_amount = 6942000;
+        let coin_7_balance =
+            coin_wrapped_7::init_register_and_mint(
+                scenario,
+                sender,
+                transfer_amount
+            );
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, sender);
+
+        // Fetch objects necessary for sending the transfer.
+        let token_bridge_state = take_state(scenario);
+
+        // Balance check the Token Bridge before executing the transfer. The
+        // initial balance should be the `transfer_amount` for COIN_WRAPPED_7.
+        {
+            let registry = state::borrow_token_registry(&token_bridge_state);
+            let asset = token_registry::borrow_wrapped<COIN_WRAPPED_7>(registry);
+            assert!(wrapped_asset::total_supply(asset) == transfer_amount, 0);
+        };
+
+        // Register and obtain a new wormhole emitter cap.
+        let emitter_cap = emitter::dummy();
+
+        let asset_info = state::verified_asset(&token_bridge_state);
+        let (
+            prepared_transfer,
+            dust
+        ) =
+            prepare_transfer(
+                &emitter_cap,
+                asset_info,
+                coin::from_balance(
+                    coin_7_balance,
+                    test_scenario::ctx(scenario)
+                ),
+                TEST_TARGET_CHAIN,
+                TEST_TARGET_RECIPIENT,
+                TEST_MESSAGE_PAYLOAD,
+                TEST_NONCE,
+            );
+        coin::destroy_zero(dust);
+
+        // Call `transfer_tokens_with_payload`.
+        let prepared_msg =
+            transfer_tokens_with_payload(
+                &mut token_bridge_state,
+                prepared_transfer
+            );
+
+        // Balance check the Token Bridge after executing the transfer. The
+        // balance should be zero, since tokens are burned when an outbound
+        // wrapped token transfer occurs.
+        {
+            let registry = state::borrow_token_registry(&token_bridge_state);
+            let asset = token_registry::borrow_wrapped<COIN_WRAPPED_7>(registry);
+            assert!(wrapped_asset::total_supply(asset) == 0, 0);
+        };
+
+        // Clean up.
+        publish_message::destroy(prepared_msg);
+        emitter::destroy_test_only(emitter_cap);
+        return_state(token_bridge_state);
+
+        // Done.
+        test_scenario::end(my_scenario);
+    }
+
+    #[test]
+    fun test_serialize_transfer_tokens_wrapped_7() {
+        use token_bridge::transfer_tokens_with_payload::{
+            bridge_in_and_serialize_transfer_test_only,
+            prepare_transfer
+        };
+
+        let sender = person();
+        let my_scenario = test_scenario::begin(sender);
+        let scenario = &mut my_scenario;
+
+        // Set up contracts.
+        let wormhole_fee = 350;
+        set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
+
+        // Register foreign emitter on chain ID == 2.
+        register_dummy_emitter(scenario, TEST_TARGET_CHAIN);
+
+        // Register and mint coins.
+        let transfer_amount = 6942000;
+        let bridged_coin_7 =
+            coin::from_balance(
+                coin_wrapped_7::init_register_and_mint(
+                    scenario,
+                    sender,
+                    transfer_amount
+                ),
+                test_scenario::ctx(scenario)
+            );
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, sender);
+
+        // Fetch objects necessary for sending the transfer.
+        let token_bridge_state = take_state(scenario);
+
+        // Register and obtain a new wormhole emitter cap.
+        let emitter_cap = emitter::dummy();
+
+        let asset_info = state::verified_asset(&token_bridge_state);
+        let expected_token_address = token_registry::token_address(&asset_info);
+        let expected_token_chain = token_registry::token_chain(&asset_info);
+
+        let (
+            prepared_transfer,
+            dust
+        ) =
+            prepare_transfer(
+                &emitter_cap,
+                asset_info,
+                bridged_coin_7,
+                TEST_TARGET_CHAIN,
+                TEST_TARGET_RECIPIENT,
+                TEST_MESSAGE_PAYLOAD,
+                TEST_NONCE,
+            );
+        coin::destroy_zero(dust);
+
+        // Serialize the payload.
+        let (
+            nonce,
+            payload
+         ) =
+            bridge_in_and_serialize_transfer_test_only(
+                &mut token_bridge_state,
+                prepared_transfer
+            );
+        assert!(nonce == TEST_NONCE, 0);
+
+        // Construct expected payload from scratch and confirm that the
+        // `transfer_tokens` call produces the same payload.
+        let expected_amount = normalized_amount::from_raw(
+            transfer_amount,
+            TEST_COIN_WRAPPED_7_DECIMALS
+        );
+
+        let expected_payload =
+            transfer_with_payload::new_test_only(
+                object::id(&emitter_cap),
+                expected_amount,
+                expected_token_address,
+                expected_token_chain,
+                 external_address::new(bytes32::from_bytes(TEST_TARGET_RECIPIENT)),
+                TEST_TARGET_CHAIN,
+                TEST_MESSAGE_PAYLOAD
+            );
+        assert!(
+            transfer_with_payload::serialize(expected_payload) == payload,
+            0
+        );
+
+        // Clean up.
+        emitter::destroy_test_only(emitter_cap);
+        return_state(token_bridge_state);
+
+        // Done.
+        test_scenario::end(my_scenario);
+    }
+
+    #[test]
+    #[expected_failure(abort_code = wormhole::package_utils::E_NOT_CURRENT_VERSION)]
+    fun test_cannot_transfer_tokens_with_payload_outdated_version() {
+        use token_bridge::transfer_tokens_with_payload::{
+            prepare_transfer,
+            transfer_tokens_with_payload
+        };
+
+        let sender = person();
+        let my_scenario = test_scenario::begin(sender);
+        let scenario = &mut my_scenario;
+
+        // Set up contracts.
+        let wormhole_fee = 350;
+        set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
+
+        // Register foreign emitter on chain ID == 2.
+        register_dummy_emitter(scenario, TEST_TARGET_CHAIN);
+
+        // Register and mint coins.
+        let transfer_amount = 6942000;
+        let coin_10_balance =
+            coin_native_10::init_register_and_mint(
+                scenario,
+                sender,
+                transfer_amount
+            );
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, sender);
+
+        // Fetch objects necessary for sending the transfer.
+        let token_bridge_state = take_state(scenario);
+
+        // Register and obtain a new wormhole emitter cap.
+        let emitter_cap = emitter::dummy();
+
+        let asset_info = state::verified_asset(&token_bridge_state);
+        let (
+            prepared_transfer,
+            dust
+        ) =
+            prepare_transfer(
+                &emitter_cap,
+                asset_info,
+                coin::from_balance(
+                    coin_10_balance,
+                    test_scenario::ctx(scenario)
+                ),
+                TEST_TARGET_CHAIN,
+                TEST_TARGET_RECIPIENT,
+                TEST_MESSAGE_PAYLOAD,
+                TEST_NONCE,
+            );
+        coin::destroy_zero(dust);
+
+        // Conveniently roll version back.
+        state::reverse_migrate_version(&mut token_bridge_state);
+
+        // Simulate executing with an outdated build by upticking the minimum
+        // required version for `publish_message` to something greater than
+        // this build.
+        state::migrate_version_test_only(
+            &mut token_bridge_state,
+            token_bridge::version_control::previous_version_test_only(),
+            token_bridge::version_control::next_version()
+        );
+
+        // You shall not pass!
+        let prepared_msg =
+            transfer_tokens_with_payload(
+                &mut token_bridge_state,
+                prepared_transfer
+            );
+
+        // Clean up.
+        publish_message::destroy(prepared_msg);
+
+        abort 42
+    }
+}

+ 48 - 0
target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/utils/coin_utils.move

@@ -0,0 +1,48 @@
+// SPDX-License-Identifier: Apache 2
+
+/// This module implements utilities helpful for outbound token transfers. These
+/// utility methods should also help avoid having to work around conversions
+/// between `Coin` and `Balance` avoiding unnecessary object creation and
+/// destruction.
+module token_bridge::coin_utils {
+    use sui::balance::{Self, Balance};
+    use sui::coin::{Self, Coin};
+    use sui::tx_context::{TxContext};
+
+    /// Method similar to `coin::take` where an amount is split from a `Coin`
+    /// object's inner balance.
+    public fun take_balance<C>(
+        coin_mut: &mut Coin<C>,
+        amount: u64
+    ): Balance<C> {
+        balance::split(coin::balance_mut(coin_mut), amount)
+    }
+
+    /// Method out of convenience to take the full balance value out of a `Coin`
+    /// object while preserving that object. This method is used to avoid
+    /// calling `coin::into_balance` which destroys the object.
+    public fun take_full_balance<C>(coin_mut: &mut Coin<C>): Balance<C> {
+        let amount = coin::value(coin_mut);
+        take_balance(coin_mut, amount)
+    }
+
+    /// Method similar to `coin::put` where an outside balance is joined with
+    /// an existing `Coin` object.
+    public fun put_balance<C>(
+        coin_mut: &mut Coin<C>,
+        the_balance: Balance<C>
+    ): u64 {
+        balance::join(coin::balance_mut(coin_mut), the_balance)
+    }
+
+    /// Method for those integrators that use `Coin` objects, where `the_coin`
+    /// will be destroyed if the value is zero. Otherwise it will be returned
+    /// back to the transaction sender.
+    public fun return_nonzero<C>(the_coin: Coin<C>, ctx: &TxContext) {
+        if (coin::value(&the_coin) == 0) {
+            coin::destroy_zero(the_coin);
+        } else {
+            sui::pay::keep(the_coin, ctx)
+        }
+    }
+}

+ 97 - 0
target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/utils/string_utils.move

@@ -0,0 +1,97 @@
+module token_bridge::string_utils {
+    use std::ascii::{Self};
+    use std::string::{Self, String};
+    use std::vector::{Self};
+
+    const QUESTION_MARK: u8 = 63;
+    // Recall that UTF-8 characters have variable-length encoding and can have
+    // 1, 2, 3, or 4 bytes.
+    // The first byte of the 2, 3, and 4-byte UTF-8 characters have a special
+    // form indicating how many more bytes follow in the same character
+    // representation. Specifically, it can have the forms
+    //  - 110xxxxx // 11000000 is 192 (base 10)
+    //  - 1110xxxx // 11100000 is 224 (base 10)
+    //  - or 11110xxx // 11110000 is 240 (base 10)
+    //
+    // We can tell the length the a hex UTF-8 character in bytes by looking
+    // at the first byte and counting the leading 1's, or alternatively
+    // seeing whether it falls in the range
+    // [11000000, 11100000) or [11100000, 11110000) or [11110000, 11111111],
+    //
+    // The following constants demarcate those ranges and are used in the
+    // string32::to_ascii function.
+    const UTF8_LENGTH_2_FIRST_BYTE_LOWER_BOUND: u8 = 192;
+    const UTF8_LENGTH_3_FIRST_BYTE_LOWER_BOUND: u8 = 224;
+    const UTF8_LENGTH_4_FIRST_BYTE_LOWER_BOUND: u8 = 240;
+
+    /// Converts a String32 to an ascii string if possible, otherwise errors
+    /// out at `ascii::string(bytes)`. For input strings that contain non-ascii
+    /// characters, we will swap the non-ascii character with `?`.
+    ///
+    /// Note that while the Sui spec limits symbols to only use ascii
+    /// characters, the token bridge spec does allow utf8 symbols.
+    public fun to_ascii(s: &String): ascii::String {
+        let buf = *string::bytes(s);
+        // keep dropping the last character while it's 0
+        while (
+            !vector::is_empty(&buf) &&
+            *vector::borrow(&buf, vector::length(&buf) - 1) == 0
+        ) {
+            vector::pop_back(&mut buf);
+        };
+
+        // Run through `buf` to convert any non-ascii character to `?`.
+        let asciified = vector::empty();
+        let (i, n) = (0, vector::length(&buf));
+        while (i < n) {
+            let b = *vector::borrow(&buf, i);
+            // If it is a valid ascii character, keep it.
+            if (ascii::is_valid_char(b)) {
+                vector::push_back(&mut asciified, b);
+                i = i + 1;
+            } else {
+                // Since UTF-8 characters have variable-length encoding (they are
+                // represented using 1-4 bytes, unlike ASCII characters, which
+                // are represented using 1 byte), we don't want to transform
+                // every byte in a UTF-8 string that does not represent an ASCII
+                // character to the question mark symbol "?". This would result
+                // in having too many "?" symbols.
+                //
+                // Instead, we want a single "?" for each character. Note that
+                // the 1-byte UTF-8 characters correspond to valid ASCII
+                // characters and have the form 0xxxxxxx.
+                // The 2, 3, and 4-byte UTF-8 characters have first byte equal
+                // to:
+                //  - 110xxxxx // 192
+                //  - 1110xxxx // 224
+                //  - or 11110xxx // 240
+                //
+                // and remaining bytes of the form:
+                // - 10xxxxxx
+                //
+                // To ensure a one-to-one mapping of a multi-byte UTF-8 character
+                // to a "?", we detect the first byte of a new UTF-8 character
+                // in a multi-byte representation by checking if it is
+                // >= 11000000 (base 2) or 192 (base 10) and convert it to a "?"
+                // and skip the remaining bytes in the same representation.
+                //
+                //
+                // Reference: https://en.wikipedia.org/wiki/UTF-8
+                if (b >= UTF8_LENGTH_2_FIRST_BYTE_LOWER_BOUND){
+                    vector::push_back(&mut asciified, QUESTION_MARK);
+                    if (b >= UTF8_LENGTH_4_FIRST_BYTE_LOWER_BOUND){
+                        // The UTF-8 char has a 4-byte hex representation.
+                        i = i + 4;
+                    } else if (b >= UTF8_LENGTH_3_FIRST_BYTE_LOWER_BOUND){
+                        // The UTF-8 char has a 3-byte hex representation.
+                        i = i + 3;
+                    } else {
+                        // The UTF-8 char has a 2-byte hex representation.
+                        i = i + 2;
+                    }
+                }
+            };
+        };
+        ascii::string(asciified)
+    }
+}

+ 351 - 0
target_chains/sui/vendor/wormhole_movement_testnet/token_bridge/sources/vaa.move

@@ -0,0 +1,351 @@
+// SPDX-License-Identifier: Apache 2
+
+/// This module builds on Wormhole's `vaa::parse_and_verify` method by adding
+/// emitter verification and replay protection.
+///
+/// Token Bridge only cares about other Token Bridge messages, so the emitter
+/// address must be a registered Token Bridge emitter according to the VAA's
+/// emitter chain ID.
+///
+/// Token Bridge does not allow replaying any of its VAAs, so its hash is stored
+/// in its `State`. If the encoded VAA passes through `parse_and_verify` again,
+/// it will abort.
+module token_bridge::vaa {
+    use sui::table::{Self};
+    use wormhole::external_address::{ExternalAddress};
+    use wormhole::vaa::{Self, VAA};
+
+    use token_bridge::state::{Self, State};
+
+    friend token_bridge::create_wrapped;
+    friend token_bridge::complete_transfer;
+    friend token_bridge::complete_transfer_with_payload;
+
+    /// For a given chain ID, Token Bridge is non-existent.
+    const E_UNREGISTERED_EMITTER: u64 = 0;
+    /// Encoded emitter address does not match registered Token Bridge.
+    const E_EMITTER_ADDRESS_MISMATCH: u64 = 1;
+
+    /// This type represents VAA data whose emitter is a registered Token Bridge
+    /// emitter. This message is also representative of a VAA that cannot be
+    /// replayed.
+    struct TokenBridgeMessage {
+        /// Wormhole chain ID from which network the message originated from.
+        emitter_chain: u16,
+        /// Address of Token Bridge (standardized to 32 bytes) that produced
+        /// this message.
+        emitter_address: ExternalAddress,
+        /// Sequence number of Token Bridge's Wormhole message.
+        sequence: u64,
+        /// Token Bridge payload.
+        payload: vector<u8>
+    }
+
+    /// Parses and verifies encoded VAA. Because Token Bridge does not allow
+    /// VAAs to be replayed, the VAA hash is stored in a set, which is checked
+    /// against the next time the same VAA is used to make sure it cannot be
+    /// used again.
+    ///
+    /// In its verification, this method checks whether the emitter is a
+    /// registered Token Bridge contract on another network.
+    ///
+    /// NOTE: It is important for integrators to refrain from calling this
+    /// method within their contracts. This method is meant to be called within
+    /// a transaction block, passing the `TokenBridgeMessage` to one of the
+    /// Token Bridge methods that consumes this type. If in a circumstance where
+    /// this module has a breaking change in an upgrade, another method  (e.g.
+    /// `complete_transfer_with_payload`) will not be affected by this change.
+    public fun verify_only_once(
+        token_bridge_state: &mut State,
+        verified_vaa: VAA
+    ): TokenBridgeMessage {
+        // This capability ensures that the current build version is used.
+        let latest_only = state::assert_latest_only(token_bridge_state);
+
+        // First parse and verify VAA using Wormhole. This also consumes the VAA
+        // hash to prevent replay.
+        vaa::consume(
+            state::borrow_mut_consumed_vaas(&latest_only, token_bridge_state),
+            &verified_vaa
+        );
+
+        // Does the emitter agree with a registered Token Bridge?
+        assert_registered_emitter(token_bridge_state, &verified_vaa);
+
+        // Take emitter info, sequence and payload.
+        let sequence = vaa::sequence(&verified_vaa);
+        let (
+            emitter_chain,
+            emitter_address,
+            payload
+        ) = vaa::take_emitter_info_and_payload(verified_vaa);
+
+        TokenBridgeMessage {
+            emitter_chain,
+            emitter_address,
+            sequence,
+            payload
+        }
+    }
+
+    public fun emitter_chain(self: &TokenBridgeMessage): u16 {
+        self.emitter_chain
+    }
+
+    public fun emitter_address(self: &TokenBridgeMessage): ExternalAddress {
+        self.emitter_address
+    }
+
+    public fun sequence(self: &TokenBridgeMessage): u64 {
+        self.sequence
+    }
+
+    /// Destroy `TokenBridgeMessage` and extract payload, which is the same
+    /// payload in the `VAA`.
+    ///
+    /// NOTE: This is a privileged method, which only friends within the Token
+    /// Bridge package can use. This guarantees that no other package can redeem
+    /// a VAA intended for Token Bridge as a denial-of-service by calling
+    /// `verify_only_once` and then destroying it by calling it this method.
+    public(friend) fun take_payload(msg: TokenBridgeMessage): vector<u8> {
+        let TokenBridgeMessage {
+            emitter_chain: _,
+            emitter_address: _,
+            sequence: _,
+            payload
+        } = msg;
+
+        payload
+    }
+
+    /// Assert that a given emitter equals one that is registered as a foreign
+    /// Token Bridge.
+    fun assert_registered_emitter(
+        token_bridge_state: &State,
+        verified_vaa: &VAA
+    ) {
+        let chain = vaa::emitter_chain(verified_vaa);
+        let registry = state::borrow_emitter_registry(token_bridge_state);
+        assert!(table::contains(registry, chain), E_UNREGISTERED_EMITTER);
+
+        let registered = table::borrow(registry, chain);
+        let emitter_addr = vaa::emitter_address(verified_vaa);
+        assert!(*registered == emitter_addr, E_EMITTER_ADDRESS_MISMATCH);
+    }
+
+    #[test_only]
+    public fun destroy(msg: TokenBridgeMessage) {
+        take_payload(msg);
+    }
+}
+
+#[test_only]
+module token_bridge::vaa_tests {
+    use sui::test_scenario::{Self};
+    use wormhole::external_address::{Self};
+    use wormhole::wormhole_scenario::{parse_and_verify_vaa};
+
+    use token_bridge::state::{Self};
+    use token_bridge::token_bridge_scenario::{
+        person,
+        register_dummy_emitter,
+        return_state,
+        set_up_wormhole_and_token_bridge,
+        take_state
+    };
+    use token_bridge::vaa::{Self};
+
+    /// VAA sent from the ethereum token bridge 0xdeadbeef.
+    const VAA: vector<u8> =
+        x"01000000000100102d399190fa61daccb11c2ea4f7a3db3a9365e5936bcda4cded87c1b9eeb095173514f226256d5579af71d4089eb89496befb998075ba94cd1d4460c5c57b84000000000100000001000200000000000000000000000000000000000000000000000000000000deadbeef0000000002634973000200000000000000000000000000000000000000000000000000000000beefface00020c0000000000000000000000000000000000000000000000000000000042454546000000000000000000000000000000000042656566206661636520546f6b656e";
+
+    #[test]
+    #[expected_failure(abort_code = vaa::E_UNREGISTERED_EMITTER)]
+    fun test_cannot_verify_only_once_unregistered_chain() {
+        let caller = person();
+        let my_scenario = test_scenario::begin(caller);
+        let scenario = &mut my_scenario;
+
+        // Set up contracts.
+        let wormhole_fee = 350;
+        set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, caller);
+
+        let token_bridge_state = take_state(scenario);
+
+        let verified_vaa = parse_and_verify_vaa(scenario, VAA);
+        // You shall not pass!
+        let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa);
+
+        // Clean up.
+        vaa::destroy(msg);
+
+        abort 42
+    }
+
+    #[test]
+    #[expected_failure(abort_code = vaa::E_EMITTER_ADDRESS_MISMATCH)]
+    fun test_cannot_verify_only_once_emitter_address_mismatch() {
+        let caller = person();
+        let my_scenario = test_scenario::begin(caller);
+        let scenario = &mut my_scenario;
+
+        // Set up contracts.
+        let wormhole_fee = 350;
+        set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, caller);
+
+        let token_bridge_state = take_state(scenario);
+
+        // First register emitter.
+        let emitter_chain = 2;
+        let emitter_addr = external_address::from_address(@0xdeafbeef);
+        token_bridge::register_chain::register_new_emitter_test_only(
+            &mut token_bridge_state,
+            emitter_chain,
+            emitter_addr
+        );
+
+        // Confirm that encoded emitter disagrees with registered emitter.
+        let verified_vaa = parse_and_verify_vaa(scenario, VAA);
+        assert!(
+            wormhole::vaa::emitter_address(&verified_vaa) != emitter_addr,
+            0
+        );
+
+        // You shall not pass!
+        let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa);
+
+        // Clean up.
+        vaa::destroy(msg);
+
+        abort 42
+    }
+
+    #[test]
+    fun test_verify_only_once() {
+        let caller = person();
+        let my_scenario = test_scenario::begin(caller);
+        let scenario = &mut my_scenario;
+
+        // Set up contracts.
+        let wormhole_fee = 350;
+        set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
+
+        // Register foreign emitter.
+        let expected_source_chain = 2;
+        register_dummy_emitter(scenario, expected_source_chain);
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, caller);
+
+        let token_bridge_state = take_state(scenario);
+
+        // Confirm VAA originated from where we expect.
+        let verified_vaa = parse_and_verify_vaa(scenario, VAA);
+        assert!(
+            wormhole::vaa::emitter_chain(&verified_vaa) == expected_source_chain,
+            0
+        );
+
+        // Finally verify.
+        let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa);
+
+        // Clean up.
+        vaa::destroy(msg);
+        return_state(token_bridge_state);
+
+        // Done.
+        test_scenario::end(my_scenario);
+    }
+
+    #[test]
+    #[expected_failure(abort_code = wormhole::set::E_KEY_ALREADY_EXISTS)]
+    fun test_cannot_verify_only_once_again() {
+        let caller = person();
+        let my_scenario = test_scenario::begin(caller);
+        let scenario = &mut my_scenario;
+
+        // Set up contracts.
+        let wormhole_fee = 350;
+        set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
+
+        // Register foreign emitter.
+        let expected_source_chain = 2;
+        register_dummy_emitter(scenario, expected_source_chain);
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, caller);
+
+        let token_bridge_state = take_state(scenario);
+
+        // Confirm VAA originated from where we expect.
+        let verified_vaa = parse_and_verify_vaa(scenario, VAA);
+        assert!(
+            wormhole::vaa::emitter_chain(&verified_vaa) == expected_source_chain,
+            0
+        );
+
+        // Finally verify.
+        let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa);
+        vaa::destroy(msg);
+
+        let verified_vaa = parse_and_verify_vaa(scenario, VAA);
+        // You shall not pass!
+        let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa);
+
+        // Clean up.
+        vaa::destroy(msg);
+
+        abort 42
+    }
+
+    #[test]
+    #[expected_failure(abort_code = wormhole::package_utils::E_NOT_CURRENT_VERSION)]
+    fun test_cannot_verify_only_once_outdated_version() {
+        let caller = person();
+        let my_scenario = test_scenario::begin(caller);
+        let scenario = &mut my_scenario;
+
+        // Set up contracts.
+        let wormhole_fee = 350;
+        set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
+
+        // Register foreign emitter.
+        let expected_source_chain = 2;
+        register_dummy_emitter(scenario, expected_source_chain);
+
+        // Ignore effects.
+        test_scenario::next_tx(scenario, caller);
+
+        let token_bridge_state = take_state(scenario);
+
+        // Verify VAA.
+        let verified_vaa = parse_and_verify_vaa(scenario, VAA);
+
+        // Conveniently roll version back.
+        state::reverse_migrate_version(&mut token_bridge_state);
+
+        // Simulate executing with an outdated build by upticking the minimum
+        // required version for `publish_message` to something greater than
+        // this build.
+        state::migrate_version_test_only(
+            &mut token_bridge_state,
+            token_bridge::version_control::previous_version_test_only(),
+            token_bridge::version_control::next_version()
+        );
+
+        // You shall not pass!
+        let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa);
+
+        // Clean up.
+        vaa::destroy(msg);
+
+        abort 42
+    }
+
+}

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio