浏览代码

add nft bridge skeleton

Change-Id: I69449e95415cd94b7de4528fe4002b241e1e6b95
Hendrik Hofstadt 4 年之前
父节点
当前提交
a2b3d111f4

+ 1 - 0
docs/devnet.md

@@ -8,6 +8,7 @@
 | Token Bridge       | ETH             | 0x0290FB167208Af455bB137780163b7B7a9a10C16                                                            |                                                                                                                                                                |
 | Test Wallet        | SOL             | 6sbzC1eH4FTujJXWj51eQe25cYvr4xfXbJ1vAj7j2k5J                                                          | Key in `solana/keys/solana-devnet.json`                                                                                                                                        |
 | Example Token      | SOL             | 2WDq7wSs9zYrpx2kbHDA4RUTRch2CCTP6ZWaH4GNfnQQ                                                          | Tokens minted to Test Wallet                                                                                                                                   |
+| Example NFT        | SOL             | BVxyYhm498L79r4HMQ9sxZ5bi41DmJmeWZ7SCS7Cyvna                                                          | One minted to Test Wallet                                                                                                                                      |
 | Bridge Core        | SOL             | Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o                                                           |                                                                                                                                                                |
 | Token Bridge       | SOL             | B6RHG3mfcckmrYN1UhmJzyS1XX3fZKbkeUcpJe9Sy3FE                                                          |                                                                                                                                                                |
 | Test Wallet        | Terra           | terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v                                                          | Mnemonic: `notice oak worry limit wrap speak medal online prefer cluster roof addict wrist behave treat actual wasp year salad speed social layer crew genius` |

+ 20 - 1
solana/devnet_setup.sh

@@ -15,6 +15,11 @@ cat <<EOF > token.json
 [179,228,102,38,68,102,75,133,127,56,63,167,143,42,59,29,220,215,100,149,220,241,176,204,154,241,168,147,195,139,55,100,22,88,9,115,146,64,160,172,3,185,132,64,254,137,133,84,142,58,166,131,205,13,77,157,245,181,101,150,105,250,163,1]
 EOF
 
+# Static key for the NFT mint so it always has the same address
+cat <<EOF > nft.json
+[155,117,110,235,96,214,56,128,109,79,49,209,212,13,134,5,43,123,213,68,21,156,128,100,95,8,43,51,188,230,21,197,156,0,108,72,200,203,243,56,73,203,7,163,249,54,21,156,197,35,249,89,28,177,153,154,189,69,137,14,197,254,233,183]
+EOF
+
 # Constants
 cli_address=6sbzC1eH4FTujJXWj51eQe25cYvr4xfXbJ1vAj7j2k5J
 bridge_address=Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o
@@ -44,7 +49,21 @@ echo "Created token account $account"
 spl-token mint "$token" 10000000000 "$account"
 
 # Create meta for token
-token-bridge-client create-meta "$token" "Solana Test Token" "SOLT"
+token-bridge-client create-meta "$token" "Solana Test Token" "SOLT" ""
+
+# Create a new SPL NFT
+nft=$(spl-token create-token --decimals 0 -- nft.json | grep 'Creating token' | awk '{ print $3 }')
+echo "Created NFT $nft"
+
+# Create NFT account
+nft_account=$(spl-token create-account "$nft" | grep 'Creating account' | awk '{ print $3 }')
+echo "Created NFT account $nft_account"
+
+# Mint new NFT owned by our CLI account
+spl-token mint "$nft" 1 "$nft_account"
+
+# Create meta for token
+token-bridge-client create-meta "$nft" "Not a PUNK" "PUNK" "https://wrappedpunks.com:3000/api/punks/metadata/39"
 
 # Create the bridge contract at a known address
 # OK to fail on subsequent attempts (already created).

+ 4047 - 0
solana/modules/nft_bridge/Cargo.lock

@@ -0,0 +1,4047 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "Inflector"
+version = "0.11.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
+dependencies = [
+ "lazy_static",
+ "regex",
+]
+
+[[package]]
+name = "addr2line"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e61f2b7f93d2c7d2b08263acaa4a363b3e276806c68af6134c44f523bf1aacd"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "ahash"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e"
+
+[[package]]
+name = "aho-corasick"
+version = "0.7.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "ansi_term"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
+dependencies = [
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.43"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28ae2b3dec75a406790005a200b1bd89785afc02517a00ca99ecfe093ee9e6cf"
+
+[[package]]
+name = "arrayref"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
+
+[[package]]
+name = "arrayvec"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
+
+[[package]]
+name = "assert_matches"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9"
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
+
+[[package]]
+name = "backtrace"
+version = "0.3.61"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7a905d892734eea339e896738c14b9afce22b5318f64b951e70bf3844419b01"
+dependencies = [
+ "addr2line",
+ "cc",
+ "cfg-if 1.0.0",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+ "serde",
+]
+
+[[package]]
+name = "base32"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa"
+
+[[package]]
+name = "base64"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7"
+
+[[package]]
+name = "base64"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff"
+
+[[package]]
+name = "base64"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
+
+[[package]]
+name = "bincode"
+version = "1.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "blake3"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b64485778c4f16a6a5a9d335e80d449ac6c70cdd6a06d2af18a6f6f775a125b3"
+dependencies = [
+ "arrayref",
+ "arrayvec",
+ "cc",
+ "cfg-if 0.1.10",
+ "constant_time_eq",
+ "crypto-mac 0.8.0",
+ "digest 0.9.0",
+]
+
+[[package]]
+name = "block-buffer"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
+dependencies = [
+ "block-padding 0.1.5",
+ "byte-tools",
+ "byteorder",
+ "generic-array 0.12.4",
+]
+
+[[package]]
+name = "block-buffer"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
+dependencies = [
+ "block-padding 0.2.1",
+ "generic-array 0.14.4",
+]
+
+[[package]]
+name = "block-padding"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5"
+dependencies = [
+ "byte-tools",
+]
+
+[[package]]
+name = "block-padding"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae"
+
+[[package]]
+name = "borsh"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09a7111f797cc721407885a323fb071636aee57f750b1a4ddc27397eba168a74"
+dependencies = [
+ "borsh-derive",
+ "hashbrown 0.9.1",
+]
+
+[[package]]
+name = "borsh-derive"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "307f3740906bac2c118a8122fe22681232b244f1369273e45f1156b45c43d2dd"
+dependencies = [
+ "borsh-derive-internal",
+ "borsh-schema-derive-internal",
+ "proc-macro-crate 0.1.5",
+ "proc-macro2 1.0.28",
+ "syn 1.0.75",
+]
+
+[[package]]
+name = "borsh-derive-internal"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2104c73179359431cc98e016998f2f23bc7a05bc53e79741bcba705f30047bc"
+dependencies = [
+ "proc-macro2 1.0.28",
+ "quote 1.0.9",
+ "syn 1.0.75",
+]
+
+[[package]]
+name = "borsh-schema-derive-internal"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae29eb8418fcd46f723f8691a2ac06857d31179d33d2f2d91eb13967de97c728"
+dependencies = [
+ "proc-macro2 1.0.28",
+ "quote 1.0.9",
+ "syn 1.0.75",
+]
+
+[[package]]
+name = "bridge"
+version = "0.1.0"
+dependencies = [
+ "borsh",
+ "byteorder",
+ "primitive-types",
+ "serde",
+ "sha3",
+ "solana-program",
+ "solitaire",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "bs58"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "476e9cd489f9e121e02ffa6014a8ef220ecb15c05ed23fc34cca13925dc283fb"
+
+[[package]]
+name = "bumpalo"
+version = "3.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631"
+
+[[package]]
+name = "bv"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8834bb1d8ee5dc048ee3124f2c7c1afcc6bc9aed03f11e9dfd8c69470a5db340"
+dependencies = [
+ "feature-probe",
+ "serde",
+]
+
+[[package]]
+name = "byte-tools"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
+
+[[package]]
+name = "byteorder"
+version = "1.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
+
+[[package]]
+name = "bytes"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c"
+dependencies = [
+ "byteorder",
+ "either",
+ "iovec",
+]
+
+[[package]]
+name = "bytes"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38"
+
+[[package]]
+name = "bytes"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040"
+
+[[package]]
+name = "bzip2"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42b7c3cbf0fa9c1b82308d57191728ca0256cb821220f4e2fd410a72ade26e3b"
+dependencies = [
+ "bzip2-sys",
+ "libc",
+]
+
+[[package]]
+name = "bzip2-sys"
+version = "0.1.11+1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+]
+
+[[package]]
+name = "cc"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2"
+dependencies = [
+ "jobserver",
+]
+
+[[package]]
+name = "cfg-if"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "chrono"
+version = "0.4.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
+dependencies = [
+ "libc",
+ "num-integer",
+ "num-traits",
+ "serde",
+ "time",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "clap"
+version = "2.33.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
+dependencies = [
+ "ansi_term",
+ "atty",
+ "bitflags",
+ "strsim",
+ "textwrap",
+ "unicode-width",
+ "vec_map",
+]
+
+[[package]]
+name = "cloudabi"
+version = "0.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "console"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c0994e656bba7b922d8dd1245db90672ffb701e684e45be58f20719d69abc5a"
+dependencies = [
+ "encode_unicode",
+ "lazy_static",
+ "libc",
+ "regex",
+ "terminal_size",
+ "termios",
+ "unicode-width",
+ "winapi 0.3.9",
+ "winapi-util",
+]
+
+[[package]]
+name = "console"
+version = "0.14.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3993e6445baa160675931ec041a5e03ca84b9c6e32a056150d3aa2bdda0a1f45"
+dependencies = [
+ "encode_unicode",
+ "lazy_static",
+ "libc",
+ "regex",
+ "terminal_size",
+ "unicode-width",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "constant_time_eq"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
+
+[[package]]
+name = "core-foundation"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b"
+
+[[package]]
+name = "cpufeatures"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "66c99696f6c9dd7f35d486b9d04d7e6e202aa3e8c40d553f2fdf5e7e0c6a71ef"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crc32fast"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a"
+dependencies = [
+ "cfg-if 1.0.0",
+]
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87"
+dependencies = [
+ "crossbeam-utils 0.7.2",
+ "maybe-uninit",
+]
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4"
+dependencies = [
+ "cfg-if 1.0.0",
+ "crossbeam-utils 0.8.5",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c20ff29ded3204c5106278a81a38f4b482636ed4fa1e6cfbeef193291beb29ed"
+dependencies = [
+ "crossbeam-epoch 0.8.2",
+ "crossbeam-utils 0.7.2",
+ "maybe-uninit",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e"
+dependencies = [
+ "cfg-if 1.0.0",
+ "crossbeam-epoch 0.9.5",
+ "crossbeam-utils 0.8.5",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace"
+dependencies = [
+ "autocfg",
+ "cfg-if 0.1.10",
+ "crossbeam-utils 0.7.2",
+ "lazy_static",
+ "maybe-uninit",
+ "memoffset 0.5.6",
+ "scopeguard",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd"
+dependencies = [
+ "cfg-if 1.0.0",
+ "crossbeam-utils 0.8.5",
+ "lazy_static",
+ "memoffset 0.6.4",
+ "scopeguard",
+]
+
+[[package]]
+name = "crossbeam-queue"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570"
+dependencies = [
+ "cfg-if 0.1.10",
+ "crossbeam-utils 0.7.2",
+ "maybe-uninit",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
+dependencies = [
+ "autocfg",
+ "cfg-if 0.1.10",
+ "lazy_static",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db"
+dependencies = [
+ "cfg-if 1.0.0",
+ "lazy_static",
+]
+
+[[package]]
+name = "crunchy"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
+
+[[package]]
+name = "crypto-mac"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5"
+dependencies = [
+ "generic-array 0.12.4",
+ "subtle 1.0.0",
+]
+
+[[package]]
+name = "crypto-mac"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab"
+dependencies = [
+ "generic-array 0.14.4",
+ "subtle 2.4.1",
+]
+
+[[package]]
+name = "crypto-mac"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "58bcd97a54c7ca5ce2f6eb16f6bede5b0ab5f0055fedc17d2f0b4466e21671ca"
+dependencies = [
+ "generic-array 0.14.4",
+ "subtle 2.4.1",
+]
+
+[[package]]
+name = "crypto-mac"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bff07008ec701e8028e2ceb8f83f0e4274ee62bd2dbdc4fefff2e9a91824081a"
+dependencies = [
+ "generic-array 0.14.4",
+ "subtle 2.4.1",
+]
+
+[[package]]
+name = "curve25519-dalek"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a9b85542f99a2dfa2a1b8e192662741c9859a846b296bef1c92ef9b58b5a216"
+dependencies = [
+ "byteorder",
+ "digest 0.8.1",
+ "rand_core 0.5.1",
+ "subtle 2.4.1",
+ "zeroize",
+]
+
+[[package]]
+name = "curve25519-dalek"
+version = "3.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61"
+dependencies = [
+ "byteorder",
+ "digest 0.9.0",
+ "rand_core 0.5.1",
+ "subtle 2.4.1",
+ "zeroize",
+]
+
+[[package]]
+name = "dashmap"
+version = "4.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c"
+dependencies = [
+ "cfg-if 1.0.0",
+ "num_cpus",
+ "rayon",
+]
+
+[[package]]
+name = "derivation-path"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "193388a8c8c75a490b604ff61775e236541b8975e98e5ca1f6ea97d122b7e2db"
+dependencies = [
+ "failure",
+]
+
+[[package]]
+name = "derivative"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
+dependencies = [
+ "proc-macro2 1.0.28",
+ "quote 1.0.9",
+ "syn 1.0.75",
+]
+
+[[package]]
+name = "dialoguer"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4aa86af7b19b40ef9cbef761ed411a49f0afa06b7b6dcd3dfe2f96a3c546138"
+dependencies = [
+ "console 0.11.3",
+ "lazy_static",
+ "tempfile",
+]
+
+[[package]]
+name = "digest"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
+dependencies = [
+ "generic-array 0.12.4",
+]
+
+[[package]]
+name = "digest"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
+dependencies = [
+ "generic-array 0.14.4",
+]
+
+[[package]]
+name = "dir-diff"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2860407d7d7e2e004bb2128510ad9e8d669e76fa005ccf567977b5d71b8b4a0b"
+dependencies = [
+ "walkdir",
+]
+
+[[package]]
+name = "dirs-next"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
+dependencies = [
+ "cfg-if 1.0.0",
+ "dirs-sys-next",
+]
+
+[[package]]
+name = "dirs-sys-next"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
+dependencies = [
+ "libc",
+ "redox_users",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "dtoa"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0"
+
+[[package]]
+name = "ed25519"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4620d40f6d2601794401d6dd95a5cf69b6c157852539470eeda433a99b3c0efc"
+dependencies = [
+ "serde",
+ "signature",
+]
+
+[[package]]
+name = "ed25519-dalek"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d"
+dependencies = [
+ "curve25519-dalek 3.2.0",
+ "ed25519",
+ "rand 0.7.3",
+ "serde",
+ "serde_bytes",
+ "sha2 0.9.5",
+ "zeroize",
+]
+
+[[package]]
+name = "ed25519-dalek-bip32"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "057f328f31294b5ab432e6c39642f54afd1531677d6d4ba2905932844cc242f3"
+dependencies = [
+ "derivation-path",
+ "ed25519-dalek",
+ "failure",
+ "hmac 0.9.0",
+ "sha2 0.9.5",
+]
+
+[[package]]
+name = "either"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
+
+[[package]]
+name = "encode_unicode"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
+
+[[package]]
+name = "encoding_rs"
+version = "0.8.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80df024fbc5ac80f87dfef0d9f5209a252f2a497f7f42944cff24d8253cac065"
+dependencies = [
+ "cfg-if 1.0.0",
+]
+
+[[package]]
+name = "env_logger"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3"
+dependencies = [
+ "atty",
+ "humantime",
+ "log",
+ "regex",
+ "termcolor",
+]
+
+[[package]]
+name = "failure"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86"
+dependencies = [
+ "backtrace",
+ "failure_derive",
+]
+
+[[package]]
+name = "failure_derive"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4"
+dependencies = [
+ "proc-macro2 1.0.28",
+ "quote 1.0.9",
+ "syn 1.0.75",
+ "synstructure",
+]
+
+[[package]]
+name = "fake-simd"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
+
+[[package]]
+name = "feature-probe"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "835a3dc7d1ec9e75e2b5fb4ba75396837112d2060b03f7d43bc1897c7f7211da"
+
+[[package]]
+name = "filetime"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "975ccf83d8d9d0d84682850a38c8169027be83368805971cc4f238c2b245bc98"
+dependencies = [
+ "cfg-if 1.0.0",
+ "libc",
+ "redox_syscall 0.2.10",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "fixed-hash"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c"
+dependencies = [
+ "static_assertions",
+]
+
+[[package]]
+name = "flate2"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd3aec53de10fe96d7d8c565eb17f2c687bb5518a2ec453b5b1252964526abe0"
+dependencies = [
+ "cfg-if 1.0.0",
+ "crc32fast",
+ "libc",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "foreign-types"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
+dependencies = [
+ "foreign-types-shared",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"
+dependencies = [
+ "matches",
+ "percent-encoding",
+]
+
+[[package]]
+name = "fuchsia-zircon"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
+dependencies = [
+ "bitflags",
+ "fuchsia-zircon-sys",
+]
+
+[[package]]
+name = "fuchsia-zircon-sys"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
+
+[[package]]
+name = "futures"
+version = "0.1.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678"
+
+[[package]]
+name = "futures"
+version = "0.3.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1adc00f486adfc9ce99f77d717836f0c5aa84965eb0b4f051f4e83f7cab53f8b"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74ed2411805f6e4e3d9bc904c95d5d423b89b3b25dc0250aa74729de20629ff9"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af51b1b4a7fdff033703db39de8802c673eb91855f2e0d47dcf3bf2c0ef01f99"
+
+[[package]]
+name = "futures-executor"
+version = "0.3.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d0d535a57b87e1ae31437b892713aee90cd2d7b0ee48727cd11fc72ef54761c"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b0e06c393068f3a6ef246c75cdca793d6a46347e75286933e5e75fd2fd11582"
+
+[[package]]
+name = "futures-macro"
+version = "0.3.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c54913bae956fb8df7f4dc6fc90362aa72e69148e3f39041fbe8742d21e0ac57"
+dependencies = [
+ "autocfg",
+ "proc-macro-hack",
+ "proc-macro2 1.0.28",
+ "quote 1.0.9",
+ "syn 1.0.75",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0f30aaa67363d119812743aa5f33c201a7a66329f97d1a887022971feea4b53"
+
+[[package]]
+name = "futures-task"
+version = "0.3.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbe54a98670017f3be909561f6ad13e810d9a51f3f061b902062ca3da80799f2"
+
+[[package]]
+name = "futures-util"
+version = "0.3.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67eb846bfd58e44a8481a00049e82c43e0ccb5d61f8dc071057cb19249dd4d78"
+dependencies = [
+ "autocfg",
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "pin-utils",
+ "proc-macro-hack",
+ "proc-macro-nested",
+ "slab",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd"
+dependencies = [
+ "typenum",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
+dependencies = [
+ "serde",
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "gethostname"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e692e296bfac1d2533ef168d0b60ff5897b8b70a4009276834014dd8924cc028"
+dependencies = [
+ "libc",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
+dependencies = [
+ "cfg-if 1.0.0",
+ "libc",
+ "wasi 0.9.0+wasi-snapshot-preview1",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
+dependencies = [
+ "cfg-if 1.0.0",
+ "libc",
+ "wasi 0.10.2+wasi-snapshot-preview1",
+]
+
+[[package]]
+name = "gimli"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0a01e0497841a3b2db4f8afa483cce65f7e96a3498bd6c541734792aeac8fe7"
+
+[[package]]
+name = "glob"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
+
+[[package]]
+name = "h2"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7f3675cfef6a30c8031cf9e6493ebdc3bb3272a3fea3923c4210d1830e6a472"
+dependencies = [
+ "bytes 1.0.1",
+ "fnv",
+ "futures-core",
+ "futures-sink",
+ "futures-util",
+ "http",
+ "indexmap",
+ "slab",
+ "tokio 1.10.0",
+ "tokio-util",
+ "tracing",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
+dependencies = [
+ "ahash",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
+[[package]]
+name = "hex-literal"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21e4590e13640f19f249fe3e4eca5113bc4289f2497710378190e7f4bd96f45b"
+
+[[package]]
+name = "hidapi"
+version = "1.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81e07da7e8614133e88b3a93b7352eb3729e3ccd82d5ab661adf23bef1761bf8"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+]
+
+[[package]]
+name = "hmac"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5dcb5e64cda4c23119ab41ba960d1e170a774c8e4b9d9e6a9bc18aabf5e59695"
+dependencies = [
+ "crypto-mac 0.7.0",
+ "digest 0.8.1",
+]
+
+[[package]]
+name = "hmac"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840"
+dependencies = [
+ "crypto-mac 0.8.0",
+ "digest 0.9.0",
+]
+
+[[package]]
+name = "hmac"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "deae6d9dbb35ec2c502d62b8f7b1c000a0822c3b0794ba36b3149c0a1c840dff"
+dependencies = [
+ "crypto-mac 0.9.1",
+ "digest 0.9.0",
+]
+
+[[package]]
+name = "hmac"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1441c6b1e930e2817404b5046f1f989899143a12bf92de603b69f4e0aee1e15"
+dependencies = [
+ "crypto-mac 0.10.1",
+ "digest 0.9.0",
+]
+
+[[package]]
+name = "hmac-drbg"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c6e570451493f10f6581b48cdd530413b63ea9e780f544bfd3bdcaa0d89d1a7b"
+dependencies = [
+ "digest 0.8.1",
+ "generic-array 0.12.4",
+ "hmac 0.7.1",
+]
+
+[[package]]
+name = "http"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "527e8c9ac747e28542699a951517aa9a6945af506cd1f2e1b53a576c17b6cc11"
+dependencies = [
+ "bytes 1.0.1",
+ "fnv",
+ "itoa",
+]
+
+[[package]]
+name = "http-body"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "399c583b2979440c60be0821a6199eca73bc3c8dcd9d070d75ac726e2c6186e5"
+dependencies = [
+ "bytes 1.0.1",
+ "http",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "httparse"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503"
+
+[[package]]
+name = "httpdate"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6456b8a6c8f33fee7d958fcd1b60d55b11940a79e63ae87013e6d22e26034440"
+
+[[package]]
+name = "humantime"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
+
+[[package]]
+name = "hyper"
+version = "0.14.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b61cf2d1aebcf6e6352c97b81dc2244ca29194be1b276f5d8ad5c6330fffb11"
+dependencies = [
+ "bytes 1.0.1",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "h2",
+ "http",
+ "http-body",
+ "httparse",
+ "httpdate",
+ "itoa",
+ "pin-project-lite",
+ "socket2 0.4.1",
+ "tokio 1.10.0",
+ "tower-service",
+ "tracing",
+ "want",
+]
+
+[[package]]
+name = "hyper-rustls"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f9f7a97316d44c0af9b0301e65010573a853a9fc97046d7331d7f6bc0fd5a64"
+dependencies = [
+ "futures-util",
+ "hyper",
+ "log",
+ "rustls",
+ "tokio 1.10.0",
+ "tokio-rustls",
+ "webpki",
+]
+
+[[package]]
+name = "idna"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
+dependencies = [
+ "matches",
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "indexmap"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5"
+dependencies = [
+ "autocfg",
+ "hashbrown 0.11.2",
+]
+
+[[package]]
+name = "indicatif"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7baab56125e25686df467fe470785512329883aab42696d661247aca2a2896e4"
+dependencies = [
+ "console 0.14.1",
+ "lazy_static",
+ "number_prefix",
+ "regex",
+]
+
+[[package]]
+name = "input_buffer"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19a8a95243d5a0398cae618ec29477c6e3cb631152be5c19481f80bc71559754"
+dependencies = [
+ "bytes 0.5.6",
+]
+
+[[package]]
+name = "instant"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bee0328b1209d157ef001c94dd85b4f8f64139adb0eac2659f4b08382b2f474d"
+dependencies = [
+ "cfg-if 1.0.0",
+]
+
+[[package]]
+name = "iovec"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "ipnet"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9"
+
+[[package]]
+name = "itertools"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
+
+[[package]]
+name = "jobserver"
+version = "0.1.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.53"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4bf49d50e2961077d9c99f4b7997d770a1114f087c3c2e0069b36c13fc2979d"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "jsonrpc-core"
+version = "17.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4467ab6dfa369b69e52bd0692e480c4d117410538526a57a304a0f2250fd95e"
+dependencies = [
+ "futures 0.3.16",
+ "futures-executor",
+ "futures-util",
+ "log",
+ "serde",
+ "serde_derive",
+ "serde_json",
+]
+
+[[package]]
+name = "keccak"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7"
+
+[[package]]
+name = "kernel32-sys"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
+dependencies = [
+ "winapi 0.2.8",
+ "winapi-build",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+dependencies = [
+ "spin",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1fa8cddc8fbbee11227ef194b5317ed014b8acbf15139bd716a18ad3fe99ec5"
+
+[[package]]
+name = "libloading"
+version = "0.6.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "351a32417a12d5f7e82c368a66781e307834dae04c6ce0cd4456d52989229883"
+dependencies = [
+ "cfg-if 1.0.0",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "libsecp256k1"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fc1e2c808481a63dc6da2074752fdd4336a3c8fcc68b83db6f1fd5224ae7962"
+dependencies = [
+ "arrayref",
+ "crunchy",
+ "digest 0.8.1",
+ "hmac-drbg",
+ "rand 0.7.3",
+ "sha2 0.8.2",
+ "subtle 2.4.1",
+ "typenum",
+]
+
+[[package]]
+name = "linked-hash-map"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
+
+[[package]]
+name = "lock_api"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75"
+dependencies = [
+ "scopeguard",
+]
+
+[[package]]
+name = "lock_api"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb"
+dependencies = [
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
+dependencies = [
+ "cfg-if 1.0.0",
+]
+
+[[package]]
+name = "matches"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
+
+[[package]]
+name = "maybe-uninit"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
+
+[[package]]
+name = "memchr"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
+
+[[package]]
+name = "memmap2"
+version = "0.1.0"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "memoffset"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "memoffset"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "mime"
+version = "0.3.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b"
+dependencies = [
+ "adler",
+ "autocfg",
+]
+
+[[package]]
+name = "mio"
+version = "0.6.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4"
+dependencies = [
+ "cfg-if 0.1.10",
+ "fuchsia-zircon",
+ "fuchsia-zircon-sys",
+ "iovec",
+ "kernel32-sys",
+ "libc",
+ "log",
+ "miow 0.2.2",
+ "net2",
+ "slab",
+ "winapi 0.2.8",
+]
+
+[[package]]
+name = "mio"
+version = "0.7.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c2bdb6314ec10835cd3293dd268473a835c02b7b352e788be788b3c6ca6bb16"
+dependencies = [
+ "libc",
+ "log",
+ "miow 0.3.7",
+ "ntapi",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "mio-uds"
+version = "0.6.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0"
+dependencies = [
+ "iovec",
+ "libc",
+ "mio 0.6.23",
+]
+
+[[package]]
+name = "miow"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d"
+dependencies = [
+ "kernel32-sys",
+ "net2",
+ "winapi 0.2.8",
+ "ws2_32-sys",
+]
+
+[[package]]
+name = "miow"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
+dependencies = [
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "native-tls"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48ba9f7719b5a0f42f338907614285fb5fd70e53858141f69898a1fb7203b24d"
+dependencies = [
+ "lazy_static",
+ "libc",
+ "log",
+ "openssl",
+ "openssl-probe",
+ "openssl-sys",
+ "schannel",
+ "security-framework",
+ "security-framework-sys",
+ "tempfile",
+]
+
+[[package]]
+name = "net2"
+version = "0.2.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae"
+dependencies = [
+ "cfg-if 0.1.10",
+ "libc",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "nft-bridge"
+version = "0.1.0"
+dependencies = [
+ "borsh",
+ "bridge",
+ "byteorder",
+ "hex",
+ "hex-literal",
+ "libsecp256k1",
+ "primitive-types",
+ "rand 0.7.3",
+ "rocksalt",
+ "serde",
+ "sha3",
+ "solana-client",
+ "solana-program",
+ "solana-sdk",
+ "solitaire",
+ "solitaire-client",
+ "spl-associated-token-account",
+ "spl-token",
+ "spl-token-metadata",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "nix"
+version = "0.19.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2ccba0cfe4fdf15982d1674c69b1fd80bad427d293849982668dfe454bd61f2"
+dependencies = [
+ "bitflags",
+ "cc",
+ "cfg-if 1.0.0",
+ "libc",
+]
+
+[[package]]
+name = "ntapi"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
+dependencies = [
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "num-derive"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d"
+dependencies = [
+ "proc-macro2 1.0.28",
+ "quote 1.0.9",
+ "syn 1.0.75",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
+[[package]]
+name = "num_enum"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9bd055fb730c4f8f4f57d45d35cd6b3f0980535b056dc7ff119cee6a66ed6f"
+dependencies = [
+ "derivative",
+ "num_enum_derive",
+]
+
+[[package]]
+name = "num_enum_derive"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "486ea01961c4a818096de679a8b740b26d9033146ac5291b1c98557658f8cdd9"
+dependencies = [
+ "proc-macro-crate 1.0.0",
+ "proc-macro2 1.0.28",
+ "quote 1.0.9",
+ "syn 1.0.75",
+]
+
+[[package]]
+name = "number_prefix"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17b02fc0ff9a9e4b35b3342880f48e896ebf69f2967921fe8646bf5b7125956a"
+
+[[package]]
+name = "object"
+version = "0.26.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee2766204889d09937d00bfbb7fec56bb2a199e2ade963cab19185d8a6104c7c"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
+dependencies = [
+ "parking_lot 0.11.1",
+]
+
+[[package]]
+name = "opaque-debug"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
+
+[[package]]
+name = "opaque-debug"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
+
+[[package]]
+name = "openssl"
+version = "0.10.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d9facdb76fec0b73c406f125d44d86fdad818d66fef0531eec9233ca425ff4a"
+dependencies = [
+ "bitflags",
+ "cfg-if 1.0.0",
+ "foreign-types",
+ "libc",
+ "once_cell",
+ "openssl-sys",
+]
+
+[[package]]
+name = "openssl-probe"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a"
+
+[[package]]
+name = "openssl-sys"
+version = "0.9.66"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1996d2d305e561b70d1ee0c53f1542833f4e1ac6ce9a6708b6ff2738ca67dc82"
+dependencies = [
+ "autocfg",
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "ouroboros"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc04551635026d3ac7bc646698ea1836a85ed2a26b7094fe1d15d8b14854c4a2"
+dependencies = [
+ "ouroboros_macro",
+ "stable_deref_trait",
+]
+
+[[package]]
+name = "ouroboros_macro"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cec33dfceabec83cd0e95a5ce9d20e76ab3a5cbfef59659b8c927f69b93ed8ae"
+dependencies = [
+ "Inflector",
+ "proc-macro2 1.0.28",
+ "quote 1.0.9",
+ "syn 1.0.75",
+]
+
+[[package]]
+name = "parking_lot"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252"
+dependencies = [
+ "lock_api 0.3.4",
+ "parking_lot_core 0.6.2",
+ "rustc_version",
+]
+
+[[package]]
+name = "parking_lot"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e"
+dependencies = [
+ "lock_api 0.3.4",
+ "parking_lot_core 0.7.2",
+]
+
+[[package]]
+name = "parking_lot"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb"
+dependencies = [
+ "instant",
+ "lock_api 0.4.4",
+ "parking_lot_core 0.8.3",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b"
+dependencies = [
+ "cfg-if 0.1.10",
+ "cloudabi",
+ "libc",
+ "redox_syscall 0.1.57",
+ "rustc_version",
+ "smallvec 0.6.14",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3"
+dependencies = [
+ "cfg-if 0.1.10",
+ "cloudabi",
+ "libc",
+ "redox_syscall 0.1.57",
+ "smallvec 1.6.1",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018"
+dependencies = [
+ "cfg-if 1.0.0",
+ "instant",
+ "libc",
+ "redox_syscall 0.2.10",
+ "smallvec 1.6.1",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "pbkdf2"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "216eaa586a190f0a738f2f918511eecfa90f13295abec0e457cdebcceda80cbd"
+dependencies = [
+ "crypto-mac 0.8.0",
+]
+
+[[package]]
+name = "pbkdf2"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b3b8c0d71734018084da0c0354193a5edfb81b20d2d57a92c5b154aefc554a4a"
+dependencies = [
+ "crypto-mac 0.10.1",
+]
+
+[[package]]
+name = "percent-encoding"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
+
+[[package]]
+name = "pest"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
+dependencies = [
+ "ucd-trie",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "pkg-config"
+version = "0.3.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
+
+[[package]]
+name = "primitive-types"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06345ee39fbccfb06ab45f3a1a5798d9dafa04cb8921a76d227040003a234b0e"
+dependencies = [
+ "fixed-hash",
+ "uint",
+]
+
+[[package]]
+name = "proc-macro-crate"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785"
+dependencies = [
+ "toml",
+]
+
+[[package]]
+name = "proc-macro-crate"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41fdbd1df62156fbc5945f4762632564d7d038153091c3fcf1067f6aef7cff92"
+dependencies = [
+ "thiserror",
+ "toml",
+]
+
+[[package]]
+name = "proc-macro-hack"
+version = "0.5.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
+
+[[package]]
+name = "proc-macro-nested"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086"
+
+[[package]]
+name = "proc-macro2"
+version = "0.4.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759"
+dependencies = [
+ "unicode-xid 0.1.0",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612"
+dependencies = [
+ "unicode-xid 0.2.2",
+]
+
+[[package]]
+name = "qstring"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d464fae65fff2680baf48019211ce37aaec0c78e9264c84a3e484717f965104e"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "quote"
+version = "0.6.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1"
+dependencies = [
+ "proc-macro2 0.4.30",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
+dependencies = [
+ "proc-macro2 1.0.28",
+]
+
+[[package]]
+name = "rand"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
+dependencies = [
+ "getrandom 0.1.16",
+ "libc",
+ "rand_chacha 0.2.2",
+ "rand_core 0.5.1",
+ "rand_hc 0.2.0",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
+dependencies = [
+ "libc",
+ "rand_chacha 0.3.1",
+ "rand_core 0.6.3",
+ "rand_hc 0.3.1",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.5.1",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.6.3",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
+dependencies = [
+ "getrandom 0.1.16",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
+dependencies = [
+ "getrandom 0.2.3",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
+dependencies = [
+ "rand_core 0.5.1",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7"
+dependencies = [
+ "rand_core 0.6.3",
+]
+
+[[package]]
+name = "rayon"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90"
+dependencies = [
+ "autocfg",
+ "crossbeam-deque 0.8.1",
+ "either",
+ "rayon-core",
+]
+
+[[package]]
+name = "rayon-core"
+version = "1.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e"
+dependencies = [
+ "crossbeam-channel 0.5.1",
+ "crossbeam-deque 0.8.1",
+ "crossbeam-utils 0.8.5",
+ "lazy_static",
+ "num_cpus",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.1.57"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
+
+[[package]]
+name = "redox_syscall"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "redox_users"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64"
+dependencies = [
+ "getrandom 0.2.3",
+ "redox_syscall 0.2.10",
+]
+
+[[package]]
+name = "regex"
+version = "1.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
+
+[[package]]
+name = "remove_dir_all"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
+dependencies = [
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "reqwest"
+version = "0.11.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "246e9f61b9bb77df069a947682be06e31ac43ea37862e244a69f177694ea6d22"
+dependencies = [
+ "base64 0.13.0",
+ "bytes 1.0.1",
+ "encoding_rs",
+ "futures-core",
+ "futures-util",
+ "http",
+ "http-body",
+ "hyper",
+ "hyper-rustls",
+ "ipnet",
+ "js-sys",
+ "lazy_static",
+ "log",
+ "mime",
+ "percent-encoding",
+ "pin-project-lite",
+ "rustls",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "tokio 1.10.0",
+ "tokio-rustls",
+ "url",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+ "webpki-roots",
+ "winreg",
+]
+
+[[package]]
+name = "ring"
+version = "0.16.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
+dependencies = [
+ "cc",
+ "libc",
+ "once_cell",
+ "spin",
+ "untrusted",
+ "web-sys",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "rocksalt"
+version = "0.1.0"
+dependencies = [
+ "byteorder",
+ "proc-macro2 1.0.28",
+ "quote 1.0.9",
+ "sha3",
+ "solana-program",
+ "syn 1.0.75",
+]
+
+[[package]]
+name = "rpassword"
+version = "4.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99371657d3c8e4d816fb6221db98fa408242b0b53bac08f8676a41f8554fe99f"
+dependencies = [
+ "libc",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "rustc-demangle"
+version = "0.1.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dead70b0b5e03e9c814bcb6b01e03e68f7c57a80aa48c72ec92152ab3e818d49"
+
+[[package]]
+name = "rustc-hash"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+
+[[package]]
+name = "rustc_version"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
+dependencies = [
+ "semver 0.9.0",
+]
+
+[[package]]
+name = "rustls"
+version = "0.19.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7"
+dependencies = [
+ "base64 0.13.0",
+ "log",
+ "ring",
+ "sct",
+ "webpki",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088"
+
+[[package]]
+name = "ryu"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "schannel"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75"
+dependencies = [
+ "lazy_static",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "scopeguard"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+
+[[package]]
+name = "sct"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce"
+dependencies = [
+ "ring",
+ "untrusted",
+]
+
+[[package]]
+name = "security-framework"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23a2ac85147a3a11d77ecf1bc7166ec0b92febfa4461c37944e180f319ece467"
+dependencies = [
+ "bitflags",
+ "core-foundation",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
+[[package]]
+name = "security-framework-sys"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e4effb91b4b8b6fb7732e670b6cee160278ff8e6bf485c7805d9e319d76e284"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "semver"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
+dependencies = [
+ "semver-parser 0.7.0",
+]
+
+[[package]]
+name = "semver"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6"
+dependencies = [
+ "semver-parser 0.10.2",
+]
+
+[[package]]
+name = "semver-parser"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
+
+[[package]]
+name = "semver-parser"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7"
+dependencies = [
+ "pest",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.128"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1056a0db1978e9dbf0f6e4fca677f6f9143dc1c19de346f22cac23e422196834"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_bytes"
+version = "0.11.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16ae07dd2f88a366f15bd0632ba725227018c69a1c8550a927324f8eb8368bb9"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.128"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13af2fbb8b60a8950d6c72a56d2095c28870367cc8e10c55e9745bac4995a2c4"
+dependencies = [
+ "proc-macro2 1.0.28",
+ "quote 1.0.9",
+ "syn 1.0.75",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.66"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "336b10da19a12ad094b59d870ebde26a45402e5b470add4b5fd03c5048a32127"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_urlencoded"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9"
+dependencies = [
+ "form_urlencoded",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_yaml"
+version = "0.8.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6375dbd828ed6964c3748e4ef6d18e7a175d408ffe184bca01698d0c73f915a9"
+dependencies = [
+ "dtoa",
+ "indexmap",
+ "serde",
+ "yaml-rust",
+]
+
+[[package]]
+name = "sha-1"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df"
+dependencies = [
+ "block-buffer 0.7.3",
+ "digest 0.8.1",
+ "fake-simd",
+ "opaque-debug 0.2.3",
+]
+
+[[package]]
+name = "sha2"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69"
+dependencies = [
+ "block-buffer 0.7.3",
+ "digest 0.8.1",
+ "fake-simd",
+ "opaque-debug 0.2.3",
+]
+
+[[package]]
+name = "sha2"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b362ae5752fd2137731f9fa25fd4d9058af34666ca1966fb969119cc35719f12"
+dependencies = [
+ "block-buffer 0.9.0",
+ "cfg-if 1.0.0",
+ "cpufeatures",
+ "digest 0.9.0",
+ "opaque-debug 0.3.0",
+]
+
+[[package]]
+name = "sha3"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809"
+dependencies = [
+ "block-buffer 0.9.0",
+ "digest 0.9.0",
+ "keccak",
+ "opaque-debug 0.3.0",
+]
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "signature"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c19772be3c4dd2ceaacf03cb41d5885f2a02c4d8804884918e3a258480803335"
+
+[[package]]
+name = "slab"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c307a32c1c5c437f38c7fd45d753050587732ba8628319fbdf12a7e289ccc590"
+
+[[package]]
+name = "smallvec"
+version = "0.6.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0"
+dependencies = [
+ "maybe-uninit",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
+
+[[package]]
+name = "socket2"
+version = "0.3.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e"
+dependencies = [
+ "cfg-if 1.0.0",
+ "libc",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "socket2"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "765f090f0e423d2b55843402a07915add955e7d60657db13707a159727326cad"
+dependencies = [
+ "libc",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "solana-account-decoder"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a72a059536988504c15627626f78a24e9cab1468978d3bddd7f9652f6b87a2f"
+dependencies = [
+ "Inflector",
+ "base64 0.12.3",
+ "bincode",
+ "bs58",
+ "bv",
+ "lazy_static",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "solana-config-program",
+ "solana-sdk",
+ "solana-stake-program",
+ "solana-vote-program",
+ "spl-token",
+ "thiserror",
+ "zstd",
+]
+
+[[package]]
+name = "solana-clap-utils"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "484288242b2b175bf2b7554497318e39b23ee921989f976387dbe48e60b2f256"
+dependencies = [
+ "chrono",
+ "clap",
+ "rpassword",
+ "solana-remote-wallet",
+ "solana-sdk",
+ "thiserror",
+ "tiny-bip39",
+ "uriparse",
+ "url",
+]
+
+[[package]]
+name = "solana-cli-config"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f23b9dc8f85fa5277c50dd00a81ee9844ecd9661ec9e66523207a477b5e755a4"
+dependencies = [
+ "dirs-next",
+ "lazy_static",
+ "serde",
+ "serde_derive",
+ "serde_yaml",
+ "url",
+]
+
+[[package]]
+name = "solana-client"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "774623776704dd9a58061eb99977b2e9552e8d6de3a92b344d1226104a888b83"
+dependencies = [
+ "base64 0.13.0",
+ "bincode",
+ "bs58",
+ "clap",
+ "indicatif",
+ "jsonrpc-core",
+ "log",
+ "net2",
+ "rayon",
+ "reqwest",
+ "semver 0.11.0",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "solana-account-decoder",
+ "solana-clap-utils",
+ "solana-faucet",
+ "solana-net-utils",
+ "solana-sdk",
+ "solana-transaction-status",
+ "solana-version",
+ "solana-vote-program",
+ "thiserror",
+ "tokio 1.10.0",
+ "tungstenite",
+ "url",
+]
+
+[[package]]
+name = "solana-config-program"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78cac22db3f711eae4b1c3259af5163370f776cd7670f3736a6c13ef558245fc"
+dependencies = [
+ "bincode",
+ "chrono",
+ "log",
+ "rand_core 0.6.3",
+ "serde",
+ "serde_derive",
+ "solana-sdk",
+]
+
+[[package]]
+name = "solana-crate-features"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61a634335bdea4898e20db749753dd4537eb343996cf1c7a132e331d84c5590d"
+dependencies = [
+ "backtrace",
+ "bytes 0.4.12",
+ "cc",
+ "curve25519-dalek 2.1.3",
+ "ed25519-dalek",
+ "either",
+ "lazy_static",
+ "libc",
+ "rand_chacha 0.2.2",
+ "regex-syntax",
+ "reqwest",
+ "ring",
+ "serde",
+ "syn 0.15.44",
+ "syn 1.0.75",
+ "tokio 0.1.22",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "solana-faucet"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed5e6adf551ca4e761c3395bb684ba5d907a051007a6dbf2d57cb99d2691e031"
+dependencies = [
+ "bincode",
+ "byteorder",
+ "clap",
+ "log",
+ "serde",
+ "serde_derive",
+ "solana-clap-utils",
+ "solana-cli-config",
+ "solana-logger",
+ "solana-metrics",
+ "solana-sdk",
+ "solana-version",
+ "spl-memo",
+ "thiserror",
+ "tokio 1.10.0",
+]
+
+[[package]]
+name = "solana-frozen-abi"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b81e60d88b1fe0322bba6f3fe6b0d7299df2f2ededa8d95ec77b934fabb967b"
+dependencies = [
+ "bs58",
+ "bv",
+ "generic-array 0.14.4",
+ "log",
+ "memmap2",
+ "rustc_version",
+ "serde",
+ "serde_derive",
+ "sha2 0.9.5",
+ "solana-frozen-abi-macro",
+ "solana-logger",
+ "thiserror",
+]
+
+[[package]]
+name = "solana-frozen-abi-macro"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f617daa0187bcc4665d63fcf9454c998e9cdad6a33181f6214558d738230bfe2"
+dependencies = [
+ "proc-macro2 1.0.28",
+ "quote 1.0.9",
+ "rustc_version",
+ "syn 1.0.75",
+]
+
+[[package]]
+name = "solana-logger"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b3e2b14bdcbb7b41de9ef5a541ac501ba3fbd07999cbcf7ea9006b3ae28b67b"
+dependencies = [
+ "env_logger",
+ "lazy_static",
+ "log",
+]
+
+[[package]]
+name = "solana-measure"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d5ad0bf2fb48761c23cb80089aba5676deaafb4a4ff0a3e2996ab39cbcbf96b4"
+dependencies = [
+ "log",
+ "solana-metrics",
+ "solana-sdk",
+]
+
+[[package]]
+name = "solana-metrics"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6880a7b453b1f6a1042fda9316ef1bca723002ab37f48a506f6f1b0448d462e8"
+dependencies = [
+ "env_logger",
+ "gethostname",
+ "lazy_static",
+ "log",
+ "reqwest",
+ "solana-sdk",
+]
+
+[[package]]
+name = "solana-net-utils"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa68e25fb6452b85733cf5c301988b56fd2d5d5a8e93c75cf38cbec06efc2eae"
+dependencies = [
+ "bincode",
+ "clap",
+ "log",
+ "nix",
+ "rand 0.7.3",
+ "serde",
+ "serde_derive",
+ "socket2 0.3.19",
+ "solana-clap-utils",
+ "solana-logger",
+ "solana-version",
+ "tokio 1.10.0",
+ "url",
+]
+
+[[package]]
+name = "solana-program"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c5d59f9d358c09db6461fae1fde6075a456685d856c004ef21af092a830e4e7"
+dependencies = [
+ "bincode",
+ "blake3",
+ "borsh",
+ "borsh-derive",
+ "bs58",
+ "bv",
+ "curve25519-dalek 2.1.3",
+ "hex",
+ "itertools",
+ "lazy_static",
+ "log",
+ "num-derive",
+ "num-traits",
+ "rand 0.7.3",
+ "rustc_version",
+ "rustversion",
+ "serde",
+ "serde_bytes",
+ "serde_derive",
+ "sha2 0.9.5",
+ "sha3",
+ "solana-frozen-abi",
+ "solana-frozen-abi-macro",
+ "solana-logger",
+ "solana-sdk-macro",
+ "thiserror",
+]
+
+[[package]]
+name = "solana-rayon-threadlimit"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7447a0206282910fc21864bcb58b80aedbf1e7c712c4eab875c5458d3bf6faf1"
+dependencies = [
+ "lazy_static",
+ "num_cpus",
+]
+
+[[package]]
+name = "solana-remote-wallet"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4d1346f89c41dc1cb9037ad427422cf3ab0b7f41915c7426f3a737094e9b2ec"
+dependencies = [
+ "base32",
+ "console 0.14.1",
+ "dialoguer",
+ "hidapi",
+ "log",
+ "num-derive",
+ "num-traits",
+ "parking_lot 0.10.2",
+ "qstring",
+ "semver 0.9.0",
+ "solana-sdk",
+ "thiserror",
+ "uriparse",
+]
+
+[[package]]
+name = "solana-runtime"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d917b1b3849e01a41ea65af89ccbd58827c8f551219769c28011a2cfd4cdd01c"
+dependencies = [
+ "arrayref",
+ "bincode",
+ "blake3",
+ "bv",
+ "byteorder",
+ "bzip2",
+ "crossbeam-channel 0.4.4",
+ "dashmap",
+ "dir-diff",
+ "flate2",
+ "fnv",
+ "itertools",
+ "lazy_static",
+ "libc",
+ "libloading",
+ "log",
+ "memmap2",
+ "num-derive",
+ "num-traits",
+ "num_cpus",
+ "ouroboros",
+ "rand 0.7.3",
+ "rayon",
+ "regex",
+ "rustc_version",
+ "serde",
+ "serde_derive",
+ "solana-config-program",
+ "solana-frozen-abi",
+ "solana-frozen-abi-macro",
+ "solana-logger",
+ "solana-measure",
+ "solana-metrics",
+ "solana-rayon-threadlimit",
+ "solana-sdk",
+ "solana-secp256k1-program",
+ "solana-stake-program",
+ "solana-vote-program",
+ "symlink",
+ "tar",
+ "tempfile",
+ "thiserror",
+ "zstd",
+]
+
+[[package]]
+name = "solana-sdk"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4223659889495ec33debeb486f424f5543b87b8f6b5548edc3b6147b4c3b9312"
+dependencies = [
+ "assert_matches",
+ "bincode",
+ "bs58",
+ "bv",
+ "byteorder",
+ "chrono",
+ "derivation-path",
+ "digest 0.9.0",
+ "ed25519-dalek",
+ "ed25519-dalek-bip32",
+ "generic-array 0.14.4",
+ "hex",
+ "hmac 0.10.1",
+ "itertools",
+ "lazy_static",
+ "libsecp256k1",
+ "log",
+ "memmap2",
+ "num-derive",
+ "num-traits",
+ "pbkdf2 0.6.0",
+ "qstring",
+ "rand 0.7.3",
+ "rand_chacha 0.2.2",
+ "rand_core 0.6.3",
+ "rustc_version",
+ "rustversion",
+ "serde",
+ "serde_bytes",
+ "serde_derive",
+ "serde_json",
+ "sha2 0.9.5",
+ "sha3",
+ "solana-crate-features",
+ "solana-frozen-abi",
+ "solana-frozen-abi-macro",
+ "solana-logger",
+ "solana-program",
+ "solana-sdk-macro",
+ "thiserror",
+ "uriparse",
+]
+
+[[package]]
+name = "solana-sdk-macro"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d27426b2a09676929c5e49df96967bbcffff003183c11a3c3ef11d78bac4aaaa"
+dependencies = [
+ "bs58",
+ "proc-macro2 1.0.28",
+ "quote 1.0.9",
+ "rustversion",
+ "syn 1.0.75",
+]
+
+[[package]]
+name = "solana-secp256k1-program"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a22f308716c2860e126bac5370f9580c4252c3cc91a58f461400267171471ea"
+dependencies = [
+ "bincode",
+ "digest 0.9.0",
+ "libsecp256k1",
+ "rand 0.7.3",
+ "sha3",
+ "solana-logger",
+ "solana-sdk",
+]
+
+[[package]]
+name = "solana-stake-program"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "152fdc283d9410175a0b8eff9f17c5627e9c913fd88e183e1a1c1b1c0e190681"
+dependencies = [
+ "bincode",
+ "log",
+ "num-derive",
+ "num-traits",
+ "rustc_version",
+ "serde",
+ "serde_derive",
+ "solana-config-program",
+ "solana-frozen-abi",
+ "solana-frozen-abi-macro",
+ "solana-metrics",
+ "solana-sdk",
+ "solana-vote-program",
+ "thiserror",
+]
+
+[[package]]
+name = "solana-transaction-status"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7dba81cb7f40b2ebf330591fab6756fa73b180729f3ef332c70f0809f70d34b3"
+dependencies = [
+ "Inflector",
+ "base64 0.12.3",
+ "bincode",
+ "bs58",
+ "lazy_static",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "solana-account-decoder",
+ "solana-runtime",
+ "solana-sdk",
+ "solana-stake-program",
+ "solana-vote-program",
+ "spl-associated-token-account",
+ "spl-memo",
+ "spl-token",
+ "thiserror",
+]
+
+[[package]]
+name = "solana-version"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df56fd1b1d2d13b387fec43c3f9e76b2c07d983535e732d5770002fdb5ee3c1f"
+dependencies = [
+ "log",
+ "rustc_version",
+ "serde",
+ "serde_derive",
+ "solana-frozen-abi",
+ "solana-frozen-abi-macro",
+ "solana-logger",
+ "solana-sdk",
+]
+
+[[package]]
+name = "solana-vote-program"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0d38c4ac44f51db1fa751cb9ff077f474157eb6137b10355d1090c2bb0072bf"
+dependencies = [
+ "bincode",
+ "log",
+ "num-derive",
+ "num-traits",
+ "rustc_version",
+ "serde",
+ "serde_derive",
+ "solana-frozen-abi",
+ "solana-frozen-abi-macro",
+ "solana-logger",
+ "solana-metrics",
+ "solana-sdk",
+ "thiserror",
+]
+
+[[package]]
+name = "solitaire"
+version = "0.1.0"
+dependencies = [
+ "borsh",
+ "byteorder",
+ "rocksalt",
+ "sha3",
+ "solana-program",
+]
+
+[[package]]
+name = "solitaire-client"
+version = "0.1.0"
+dependencies = [
+ "borsh",
+ "solana-program",
+ "solana-sdk",
+ "solitaire",
+]
+
+[[package]]
+name = "spin"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
+
+[[package]]
+name = "spl-associated-token-account"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4adc47eebe5d2b662cbaaba1843719c28a67e5ec5d0460bc3ca60900a51f74e2"
+dependencies = [
+ "solana-program",
+ "spl-token",
+]
+
+[[package]]
+name = "spl-memo"
+version = "3.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd0dc6f70db6bacea7ff25870b016a65ba1d1b6013536f08e4fd79a8f9005325"
+dependencies = [
+ "solana-program",
+]
+
+[[package]]
+name = "spl-token"
+version = "3.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b795e50d15dfd35aa5460b80a16414503a322be115a417a43db987c5824c6798"
+dependencies = [
+ "arrayref",
+ "num-derive",
+ "num-traits",
+ "num_enum",
+ "solana-program",
+ "thiserror",
+]
+
+[[package]]
+name = "spl-token-metadata"
+version = "0.0.1"
+dependencies = [
+ "borsh",
+ "solana-program",
+ "spl-token",
+]
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
+
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
+name = "strsim"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
+
+[[package]]
+name = "subtle"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee"
+
+[[package]]
+name = "subtle"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
+
+[[package]]
+name = "symlink"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a"
+
+[[package]]
+name = "syn"
+version = "0.15.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5"
+dependencies = [
+ "proc-macro2 0.4.30",
+ "quote 0.6.13",
+ "unicode-xid 0.1.0",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.75"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7f58f7e8eaa0009c5fec437aabf511bd9933e4b2d7407bd05273c01a8906ea7"
+dependencies = [
+ "proc-macro2 1.0.28",
+ "quote 1.0.9",
+ "unicode-xid 0.2.2",
+]
+
+[[package]]
+name = "synstructure"
+version = "0.12.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "474aaa926faa1603c40b7885a9eaea29b444d1cb2850cb7c0e37bb1a4182f4fa"
+dependencies = [
+ "proc-macro2 1.0.28",
+ "quote 1.0.9",
+ "syn 1.0.75",
+ "unicode-xid 0.2.2",
+]
+
+[[package]]
+name = "tar"
+version = "0.4.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6f5515d3add52e0bbdcad7b83c388bb36ba7b754dda3b5f5bc2d38640cdba5c"
+dependencies = [
+ "filetime",
+ "libc",
+ "xattr",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22"
+dependencies = [
+ "cfg-if 1.0.0",
+ "libc",
+ "rand 0.8.4",
+ "redox_syscall 0.2.10",
+ "remove_dir_all",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "terminal_size"
+version = "0.1.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df"
+dependencies = [
+ "libc",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "termios"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "411c5bf740737c7918b8b1fe232dca4dc9f8e754b8ad5e20966814001ed0ac6b"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
+dependencies = [
+ "unicode-width",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745"
+dependencies = [
+ "proc-macro2 1.0.28",
+ "quote 1.0.9",
+ "syn 1.0.75",
+]
+
+[[package]]
+name = "time"
+version = "0.1.43"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
+dependencies = [
+ "libc",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "tiny-bip39"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9e44c4759bae7f1032e286a7ef990bd9ed23fe831b7eeba0beb97484c2e59b8"
+dependencies = [
+ "anyhow",
+ "hmac 0.8.1",
+ "once_cell",
+ "pbkdf2 0.4.0",
+ "rand 0.7.3",
+ "rustc-hash",
+ "sha2 0.9.5",
+ "thiserror",
+ "unicode-normalization",
+ "zeroize",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "848a1e1181b9f6753b5e96a092749e29b11d19ede67dfbbd6c7dc7e0f49b5338"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
+
+[[package]]
+name = "tokio"
+version = "0.1.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a09c0b5bb588872ab2f09afa13ee6e9dac11e10a0ec9e8e3ba39a5a5d530af6"
+dependencies = [
+ "bytes 0.4.12",
+ "futures 0.1.31",
+ "mio 0.6.23",
+ "num_cpus",
+ "tokio-codec",
+ "tokio-current-thread",
+ "tokio-executor",
+ "tokio-fs",
+ "tokio-io",
+ "tokio-reactor",
+ "tokio-sync",
+ "tokio-tcp",
+ "tokio-threadpool",
+ "tokio-timer",
+ "tokio-udp",
+ "tokio-uds",
+]
+
+[[package]]
+name = "tokio"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01cf844b23c6131f624accf65ce0e4e9956a8bb329400ea5bcc26ae3a5c20b0b"
+dependencies = [
+ "autocfg",
+ "bytes 1.0.1",
+ "libc",
+ "memchr",
+ "mio 0.7.13",
+ "num_cpus",
+ "once_cell",
+ "parking_lot 0.11.1",
+ "pin-project-lite",
+ "signal-hook-registry",
+ "tokio-macros",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "tokio-codec"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25b2998660ba0e70d18684de5d06b70b70a3a747469af9dea7618cc59e75976b"
+dependencies = [
+ "bytes 0.4.12",
+ "futures 0.1.31",
+ "tokio-io",
+]
+
+[[package]]
+name = "tokio-current-thread"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1de0e32a83f131e002238d7ccde18211c0a5397f60cbfffcb112868c2e0e20e"
+dependencies = [
+ "futures 0.1.31",
+ "tokio-executor",
+]
+
+[[package]]
+name = "tokio-executor"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb2d1b8f4548dbf5e1f7818512e9c406860678f29c300cdf0ebac72d1a3a1671"
+dependencies = [
+ "crossbeam-utils 0.7.2",
+ "futures 0.1.31",
+]
+
+[[package]]
+name = "tokio-fs"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "297a1206e0ca6302a0eed35b700d292b275256f596e2f3fea7729d5e629b6ff4"
+dependencies = [
+ "futures 0.1.31",
+ "tokio-io",
+ "tokio-threadpool",
+]
+
+[[package]]
+name = "tokio-io"
+version = "0.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674"
+dependencies = [
+ "bytes 0.4.12",
+ "futures 0.1.31",
+ "log",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54473be61f4ebe4efd09cec9bd5d16fa51d70ea0192213d754d2d500457db110"
+dependencies = [
+ "proc-macro2 1.0.28",
+ "quote 1.0.9",
+ "syn 1.0.75",
+]
+
+[[package]]
+name = "tokio-reactor"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09bc590ec4ba8ba87652da2068d150dcada2cfa2e07faae270a5e0409aa51351"
+dependencies = [
+ "crossbeam-utils 0.7.2",
+ "futures 0.1.31",
+ "lazy_static",
+ "log",
+ "mio 0.6.23",
+ "num_cpus",
+ "parking_lot 0.9.0",
+ "slab",
+ "tokio-executor",
+ "tokio-io",
+ "tokio-sync",
+]
+
+[[package]]
+name = "tokio-rustls"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6"
+dependencies = [
+ "rustls",
+ "tokio 1.10.0",
+ "webpki",
+]
+
+[[package]]
+name = "tokio-sync"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edfe50152bc8164fcc456dab7891fa9bf8beaf01c5ee7e1dd43a397c3cf87dee"
+dependencies = [
+ "fnv",
+ "futures 0.1.31",
+]
+
+[[package]]
+name = "tokio-tcp"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "98df18ed66e3b72e742f185882a9e201892407957e45fbff8da17ae7a7c51f72"
+dependencies = [
+ "bytes 0.4.12",
+ "futures 0.1.31",
+ "iovec",
+ "mio 0.6.23",
+ "tokio-io",
+ "tokio-reactor",
+]
+
+[[package]]
+name = "tokio-threadpool"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df720b6581784c118f0eb4310796b12b1d242a7eb95f716a8367855325c25f89"
+dependencies = [
+ "crossbeam-deque 0.7.4",
+ "crossbeam-queue",
+ "crossbeam-utils 0.7.2",
+ "futures 0.1.31",
+ "lazy_static",
+ "log",
+ "num_cpus",
+ "slab",
+ "tokio-executor",
+]
+
+[[package]]
+name = "tokio-timer"
+version = "0.2.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93044f2d313c95ff1cb7809ce9a7a05735b012288a888b62d4434fd58c94f296"
+dependencies = [
+ "crossbeam-utils 0.7.2",
+ "futures 0.1.31",
+ "slab",
+ "tokio-executor",
+]
+
+[[package]]
+name = "tokio-udp"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2a0b10e610b39c38b031a2fcab08e4b82f16ece36504988dcbd81dbba650d82"
+dependencies = [
+ "bytes 0.4.12",
+ "futures 0.1.31",
+ "log",
+ "mio 0.6.23",
+ "tokio-codec",
+ "tokio-io",
+ "tokio-reactor",
+]
+
+[[package]]
+name = "tokio-uds"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab57a4ac4111c8c9dbcf70779f6fc8bc35ae4b2454809febac840ad19bd7e4e0"
+dependencies = [
+ "bytes 0.4.12",
+ "futures 0.1.31",
+ "iovec",
+ "libc",
+ "log",
+ "mio 0.6.23",
+ "mio-uds",
+ "tokio-codec",
+ "tokio-io",
+ "tokio-reactor",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.6.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1caa0b0c8d94a049db56b5acf8cba99dc0623aab1b26d5b5f5e2d945846b3592"
+dependencies = [
+ "bytes 1.0.1",
+ "futures-core",
+ "futures-sink",
+ "log",
+ "pin-project-lite",
+ "tokio 1.10.0",
+]
+
+[[package]]
+name = "toml"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "tower-service"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6"
+
+[[package]]
+name = "tracing"
+version = "0.1.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09adeb8c97449311ccd28a427f96fb563e7fd31aabf994189879d9da2394b89d"
+dependencies = [
+ "cfg-if 1.0.0",
+ "pin-project-lite",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ca517f43f0fb96e0c3072ed5c275fe5eece87e8cb52f4a77b69226d3b1c9df8"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "try-lock"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
+
+[[package]]
+name = "tungstenite"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfea31758bf674f990918962e8e5f07071a3161bd7c4138ed23e416e1ac4264e"
+dependencies = [
+ "base64 0.11.0",
+ "byteorder",
+ "bytes 0.5.6",
+ "http",
+ "httparse",
+ "input_buffer",
+ "log",
+ "native-tls",
+ "rand 0.7.3",
+ "sha-1",
+ "url",
+ "utf-8",
+]
+
+[[package]]
+name = "typenum"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06"
+
+[[package]]
+name = "ucd-trie"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
+
+[[package]]
+name = "uint"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6470ab50f482bde894a037a57064480a246dbfdd5960bd65a44824693f08da5f"
+dependencies = [
+ "byteorder",
+ "crunchy",
+ "hex",
+ "static_assertions",
+]
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "246f4c42e67e7a4e3c6106ff716a5d067d4132a642840b242e357e468a2a0085"
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "unicode-width"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
+
+[[package]]
+name = "unicode-xid"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
+
+[[package]]
+name = "untrusted"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
+
+[[package]]
+name = "uriparse"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e515b1ada404168e145ac55afba3c42f04cf972201a8552d42e2abb17c1b7221"
+dependencies = [
+ "fnv",
+ "lazy_static",
+]
+
+[[package]]
+name = "url"
+version = "2.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "matches",
+ "percent-encoding",
+]
+
+[[package]]
+name = "utf-8"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
+
+[[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
+[[package]]
+name = "vec_map"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
+
+[[package]]
+name = "version_check"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
+
+[[package]]
+name = "walkdir"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
+dependencies = [
+ "same-file",
+ "winapi 0.3.9",
+ "winapi-util",
+]
+
+[[package]]
+name = "want"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0"
+dependencies = [
+ "log",
+ "try-lock",
+]
+
+[[package]]
+name = "wasi"
+version = "0.9.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
+
+[[package]]
+name = "wasi"
+version = "0.10.2+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.76"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ce9b1b516211d33767048e5d47fa2a381ed8b76fc48d2ce4aa39877f9f183e0"
+dependencies = [
+ "cfg-if 1.0.0",
+ "serde",
+ "serde_json",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.76"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfe8dc78e2326ba5f845f4b5bf548401604fa20b1dd1d365fb73b6c1d6364041"
+dependencies = [
+ "bumpalo",
+ "lazy_static",
+ "log",
+ "proc-macro2 1.0.28",
+ "quote 1.0.9",
+ "syn 1.0.75",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95fded345a6559c2cfee778d562300c581f7d4ff3edb9b0d230d69800d213972"
+dependencies = [
+ "cfg-if 1.0.0",
+ "js-sys",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.76"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44468aa53335841d9d6b6c023eaab07c0cd4bddbcfdee3e2bb1e8d2cb8069fef"
+dependencies = [
+ "quote 1.0.9",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.76"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0195807922713af1e67dc66132c7328206ed9766af3858164fb583eedc25fbad"
+dependencies = [
+ "proc-macro2 1.0.28",
+ "quote 1.0.9",
+ "syn 1.0.75",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.76"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acdb075a845574a1fa5f09fd77e43f7747599301ea3417a9fbffdeedfc1f4a29"
+
+[[package]]
+name = "web-sys"
+version = "0.3.53"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "224b2f6b67919060055ef1a67807367c2066ed520c3862cc013d26cf893a783c"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "webpki"
+version = "0.21.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea"
+dependencies = [
+ "ring",
+ "untrusted",
+]
+
+[[package]]
+name = "webpki-roots"
+version = "0.21.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940"
+dependencies = [
+ "webpki",
+]
+
+[[package]]
+name = "winapi"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-build"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+dependencies = [
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "winreg"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69"
+dependencies = [
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "ws2_32-sys"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
+dependencies = [
+ "winapi 0.2.8",
+ "winapi-build",
+]
+
+[[package]]
+name = "xattr"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "244c3741f4240ef46274860397c7c74e50eb23624996930e484c16679633a54c"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "yaml-rust"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
+dependencies = [
+ "linked-hash-map",
+]
+
+[[package]]
+name = "zeroize"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "377db0846015f7ae377174787dd452e1c5f5a9050bc6f954911d01f116daa0cd"
+dependencies = [
+ "zeroize_derive",
+]
+
+[[package]]
+name = "zeroize_derive"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2c1e130bebaeab2f23886bf9acbaca14b092408c452543c857f66399cd6dab1"
+dependencies = [
+ "proc-macro2 1.0.28",
+ "quote 1.0.9",
+ "syn 1.0.75",
+ "synstructure",
+]
+
+[[package]]
+name = "zstd"
+version = "0.5.4+zstd.1.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69996ebdb1ba8b1517f61387a883857818a66c8a295f487b1ffd8fd9d2c82910"
+dependencies = [
+ "zstd-safe",
+]
+
+[[package]]
+name = "zstd-safe"
+version = "2.0.6+zstd.1.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "98aa931fb69ecee256d44589d19754e61851ae4769bf963b385119b1cc37a49e"
+dependencies = [
+ "libc",
+ "zstd-sys",
+]
+
+[[package]]
+name = "zstd-sys"
+version = "1.4.18+zstd.1.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1e6e8778706838f43f771d80d37787cb2fe06dafe89dd3aebaf6721b9eaec81"
+dependencies = [
+ "cc",
+ "glob",
+ "itertools",
+ "libc",
+]

+ 5 - 0
solana/modules/nft_bridge/Cargo.toml

@@ -0,0 +1,5 @@
+[workspace]
+members = ["program"]
+
+[patch.crates-io]
+memmap2 = { path = "../../bridge/memmap2-rs" }

+ 43 - 0
solana/modules/nft_bridge/program/Cargo.toml

@@ -0,0 +1,43 @@
+[package]
+name = "nft-bridge"
+version = "0.1.0"
+description = "Created with Rocksalt"
+edition = "2018"
+
+[lib]
+crate-type = ["cdylib", "lib"]
+name = "nft_bridge"
+
+[features]
+no-entrypoint = ["solitaire/no-entrypoint", "rand"]
+trace = ["solitaire/trace"]
+wasm = ["no-entrypoint"]
+client = ["solitaire-client", "solitaire/client", "no-entrypoint"]
+cpi = ["no-entrypoint"]
+default = []
+
+[dependencies]
+bridge = { path = "../../../bridge/program", features = ["no-entrypoint", "cpi"] }
+borsh = "0.8.1"
+byteorder = "1.4.3"
+rocksalt = { path = "../../../solitaire/rocksalt" }
+solitaire = { path = "../../../solitaire/program" }
+sha3 = "0.9.1"
+solana-program = "*"
+spl-token = { version = "=3.1.0", features = ["no-entrypoint"] }
+spl-associated-token-account = { version = "1.0.2" }
+primitive-types = { version = "0.9.0", default-features = false }
+solitaire-client = { path = "../../../solitaire/client", optional = true }
+spl-token-metadata = { path = "../../token_bridge/token-metadata" }
+wasm-bindgen = { version = "0.2.74", features = ["serde-serialize"] }
+serde = { version = "1.0", features = ["derive"] }
+rand = { version = "0.7.3", optional = true }
+
+[dev-dependencies]
+hex = "*"
+hex-literal = "0.3.1"
+libsecp256k1 = { version = "0.3.5", features = [] }
+solana-client = "1.7.0"
+solana-sdk = "=1.7.0"
+spl-token = { version = "=3.1.0", features = ["no-entrypoint"] }
+spl-token-metadata = { path = "../../token_bridge/token-metadata" }

+ 2 - 0
solana/modules/nft_bridge/program/Xargo.toml

@@ -0,0 +1,2 @@
+[target.bpfel-unknown-unknown.dependencies.std]
+features = []

+ 112 - 0
solana/modules/nft_bridge/program/src/accounts.rs

@@ -0,0 +1,112 @@
+use crate::types::*;
+use bridge::{
+    api::ForeignAddress,
+    types::BridgeData,
+    vaa::{
+        DeserializePayload,
+        PayloadMessage,
+    },
+};
+use primitive_types::U256;
+use solana_program::pubkey::Pubkey;
+use solitaire::{
+    processors::seeded::Seeded,
+    *,
+};
+
+pub type AuthoritySigner<'b> = Derive<Info<'b>, "authority_signer">;
+pub type CustodySigner<'b> = Derive<Info<'b>, "custody_signer">;
+pub type MintSigner<'b> = Derive<Info<'b>, "mint_signer">;
+
+pub type CoreBridge<'a, const State: AccountState> = Data<'a, BridgeData, { State }>;
+
+pub type EmitterAccount<'b> = Derive<Info<'b>, "emitter">;
+
+pub type ConfigAccount<'b, const State: AccountState> =
+    Derive<Data<'b, Config, { State }>, "config">;
+
+pub type CustodyAccount<'b, const State: AccountState> = Data<'b, SplAccount, { State }>;
+
+pub struct CustodyAccountDerivationData {
+    pub mint: Pubkey,
+}
+
+impl<'b, const State: AccountState> Seeded<&CustodyAccountDerivationData>
+    for CustodyAccount<'b, { State }>
+{
+    fn seeds(accs: &CustodyAccountDerivationData) -> Vec<Vec<u8>> {
+        vec![accs.mint.to_bytes().to_vec()]
+    }
+}
+
+pub type WrappedMint<'b, const State: AccountState> = Data<'b, SplMint, { State }>;
+
+pub struct WrappedDerivationData {
+    pub token_chain: ChainID,
+    pub token_address: ForeignAddress,
+    pub token_id: U256,
+}
+
+impl<'b, const State: AccountState> Seeded<&WrappedDerivationData> for WrappedMint<'b, { State }> {
+    fn seeds(data: &WrappedDerivationData) -> Vec<Vec<u8>> {
+        let mut token_id = vec![0u8; 32];
+        data.token_id.to_big_endian(&mut token_id);
+        vec![
+            String::from("wrapped").as_bytes().to_vec(),
+            data.token_chain.to_be_bytes().to_vec(),
+            data.token_address.to_vec(),
+            token_id,
+        ]
+    }
+}
+
+pub type WrappedTokenMeta<'b, const State: AccountState> = Data<'b, WrappedMeta, { State }>;
+
+pub struct WrappedMetaDerivationData {
+    pub mint_key: Pubkey,
+}
+
+impl<'b, const State: AccountState> Seeded<&WrappedMetaDerivationData>
+    for WrappedTokenMeta<'b, { State }>
+{
+    fn seeds(data: &WrappedMetaDerivationData) -> Vec<Vec<u8>> {
+        vec![
+            String::from("meta").as_bytes().to_vec(),
+            data.mint_key.to_bytes().to_vec(),
+        ]
+    }
+}
+
+/// Registered chain endpoint
+pub type Endpoint<'b, const State: AccountState> = Data<'b, EndpointRegistration, { State }>;
+
+pub struct EndpointDerivationData {
+    pub emitter_chain: u16,
+    pub emitter_address: ForeignAddress,
+}
+
+/// Seeded implementation based on an incoming VAA
+impl<'b, const State: AccountState> Seeded<&EndpointDerivationData> for Endpoint<'b, { State }> {
+    fn seeds(data: &EndpointDerivationData) -> Vec<Vec<u8>> {
+        vec![
+            data.emitter_chain.to_be_bytes().to_vec(),
+            data.emitter_address.to_vec(),
+        ]
+    }
+}
+
+pub type SplTokenMeta<'b> = Info<'b>;
+
+pub struct SplTokenMetaDerivationData {
+    pub mint: Pubkey,
+}
+
+impl<'b> Seeded<&SplTokenMetaDerivationData> for SplTokenMeta<'b> {
+    fn seeds(data: &SplTokenMetaDerivationData) -> Vec<Vec<u8>> {
+        vec![
+            "metadata".as_bytes().to_vec(),
+            spl_token_metadata::id().as_ref().to_vec(),
+            data.mint.as_ref().to_vec(),
+        ]
+    }
+}

+ 9 - 0
solana/modules/nft_bridge/program/src/api.rs

@@ -0,0 +1,9 @@
+pub mod complete_transfer;
+pub mod governance;
+pub mod initialize;
+pub mod transfer;
+
+pub use complete_transfer::*;
+pub use governance::*;
+pub use initialize::*;
+pub use transfer::*;

+ 323 - 0
solana/modules/nft_bridge/program/src/api/complete_transfer.rs

@@ -0,0 +1,323 @@
+use crate::{
+    accounts::{
+        ConfigAccount,
+        CustodyAccount,
+        CustodyAccountDerivationData,
+        CustodySigner,
+        Endpoint,
+        EndpointDerivationData,
+        MintSigner,
+        SplTokenMeta,
+        SplTokenMetaDerivationData,
+        WrappedDerivationData,
+        WrappedMetaDerivationData,
+        WrappedMint,
+        WrappedTokenMeta,
+    },
+    messages::PayloadTransfer,
+    types::*,
+    TokenBridgeError::*,
+};
+use bridge::{
+    vaa::ClaimableVAA,
+    CHAIN_ID_SOLANA,
+};
+use solana_program::{
+    account_info::AccountInfo,
+    program::{
+        invoke,
+        invoke_signed,
+    },
+    program_error::ProgramError,
+    pubkey::Pubkey,
+};
+use solitaire::{
+    processors::seeded::{
+        invoke_seeded,
+        Seeded,
+    },
+    CreationLamports::Exempt,
+    *,
+};
+use spl_token::state::{
+    Account,
+    Mint,
+};
+use std::ops::{
+    Deref,
+    DerefMut,
+};
+
+#[derive(FromAccounts)]
+pub struct CompleteNative<'b> {
+    pub payer: Mut<Signer<AccountInfo<'b>>>,
+    pub config: ConfigAccount<'b, { AccountState::Initialized }>,
+
+    pub vaa: ClaimableVAA<'b, PayloadTransfer>,
+    pub chain_registration: Endpoint<'b, { AccountState::Initialized }>,
+
+    pub to: Mut<Data<'b, SplAccount, { AccountState::MaybeInitialized }>>,
+    pub to_authority: MaybeMut<Info<'b>>,
+    pub custody: Mut<CustodyAccount<'b, { AccountState::Initialized }>>,
+    pub mint: Data<'b, SplMint, { AccountState::Initialized }>,
+
+    pub custody_signer: CustodySigner<'b>,
+}
+
+impl<'a> From<&CompleteNative<'a>> for EndpointDerivationData {
+    fn from(accs: &CompleteNative<'a>) -> Self {
+        EndpointDerivationData {
+            emitter_chain: accs.vaa.meta().emitter_chain,
+            emitter_address: accs.vaa.meta().emitter_address,
+        }
+    }
+}
+
+impl<'a> From<&CompleteNative<'a>> for CustodyAccountDerivationData {
+    fn from(accs: &CompleteNative<'a>) -> Self {
+        CustodyAccountDerivationData {
+            mint: *accs.mint.info().key,
+        }
+    }
+}
+
+impl<'b> InstructionContext<'b> for CompleteNative<'b> {}
+
+#[derive(BorshDeserialize, BorshSerialize, Default)]
+pub struct CompleteNativeData {}
+
+pub fn complete_native(
+    ctx: &ExecutionContext,
+    accs: &mut CompleteNative,
+    data: CompleteNativeData,
+) -> Result<()> {
+    // Verify the chain registration
+    let derivation_data: EndpointDerivationData = (&*accs).into();
+    accs.chain_registration
+        .verify_derivation(ctx.program_id, &derivation_data)?;
+
+    // Verify that the custody account is derived correctly
+    let derivation_data: CustodyAccountDerivationData = (&*accs).into();
+    accs.custody
+        .verify_derivation(ctx.program_id, &derivation_data)?;
+
+    // Verify mints
+    if *accs.mint.info().key != accs.custody.mint {
+        return Err(InvalidMint.into());
+    }
+    if *accs.custody_signer.key != accs.custody.owner {
+        return Err(WrongAccountOwner.into());
+    }
+
+    // Verify VAA
+    if accs.vaa.token_address != accs.mint.info().key.to_bytes() {
+        return Err(InvalidMint.into());
+    }
+    if accs.vaa.token_chain != CHAIN_ID_SOLANA {
+        return Err(InvalidChain.into());
+    }
+    if accs.vaa.to_chain != CHAIN_ID_SOLANA {
+        return Err(InvalidChain.into());
+    }
+
+    // Prevent vaa double signing
+    accs.vaa.verify(ctx.program_id)?;
+    accs.vaa.claim(ctx, accs.payer.key)?;
+
+    if !accs.to.is_initialized() {
+        let associated_addr = spl_associated_token_account::get_associated_token_address(
+            accs.to_authority.info().key,
+            accs.mint.info().key,
+        );
+        if *accs.to_authority.info().key != associated_addr {
+            return Err(InvalidAssociatedAccount.into());
+        }
+        // Create associated token account
+        let ix = spl_associated_token_account::create_associated_token_account(
+            accs.payer.info().key,
+            accs.to_authority.info().key,
+            accs.mint.info().key,
+        );
+        invoke(&ix, ctx.accounts)?;
+    } else if *accs.mint.info().key != accs.to.mint {
+        return Err(InvalidMint.into());
+    }
+
+    // Transfer tokens
+    let transfer_ix = spl_token::instruction::transfer(
+        &spl_token::id(),
+        accs.custody.info().key,
+        accs.to.info().key,
+        accs.custody_signer.key,
+        &[],
+        1,
+    )?;
+    invoke_seeded(&transfer_ix, ctx, &accs.custody_signer, None)?;
+
+    Ok(())
+}
+
+#[derive(FromAccounts)]
+pub struct CompleteWrapped<'b> {
+    pub payer: Mut<Signer<AccountInfo<'b>>>,
+    pub config: ConfigAccount<'b, { AccountState::Initialized }>,
+
+    // Signed message for the transfer
+    pub vaa: ClaimableVAA<'b, PayloadTransfer>,
+
+    pub chain_registration: Endpoint<'b, { AccountState::Initialized }>,
+
+    pub to: Mut<Data<'b, SplAccount, { AccountState::MaybeInitialized }>>,
+    pub to_authority: MaybeMut<Info<'b>>,
+    pub mint: Mut<WrappedMint<'b, { AccountState::MaybeInitialized }>>,
+    pub meta: Mut<WrappedTokenMeta<'b, { AccountState::MaybeInitialized }>>,
+
+    /// SPL Metadata for the associated Mint
+    pub spl_metadata: Mut<SplTokenMeta<'b>>,
+
+    pub mint_authority: MintSigner<'b>,
+}
+
+impl<'a> From<&CompleteWrapped<'a>> for EndpointDerivationData {
+    fn from(accs: &CompleteWrapped<'a>) -> Self {
+        EndpointDerivationData {
+            emitter_chain: accs.vaa.meta().emitter_chain,
+            emitter_address: accs.vaa.meta().emitter_address,
+        }
+    }
+}
+
+impl<'a> From<&CompleteWrapped<'a>> for WrappedDerivationData {
+    fn from(accs: &CompleteWrapped<'a>) -> Self {
+        WrappedDerivationData {
+            token_chain: accs.vaa.token_chain,
+            token_address: accs.vaa.token_address,
+            token_id: accs.vaa.token_id,
+        }
+    }
+}
+
+impl<'a> From<&CompleteWrapped<'a>> for WrappedMetaDerivationData {
+    fn from(accs: &CompleteWrapped<'a>) -> Self {
+        WrappedMetaDerivationData {
+            mint_key: *accs.mint.info().key,
+        }
+    }
+}
+
+impl<'b> InstructionContext<'b> for CompleteWrapped<'b> {}
+
+#[derive(BorshDeserialize, BorshSerialize, Default)]
+pub struct CompleteWrappedData {}
+
+pub fn complete_wrapped(
+    ctx: &ExecutionContext,
+    accs: &mut CompleteWrapped,
+    data: CompleteWrappedData,
+) -> Result<()> {
+    // Verify the chain registration
+    let derivation_data: EndpointDerivationData = (&*accs).into();
+    accs.chain_registration
+        .verify_derivation(ctx.program_id, &derivation_data)?;
+
+    // Verify mint
+    let derivation_data: WrappedDerivationData = (&*accs).into();
+    accs.mint
+        .verify_derivation(ctx.program_id, &derivation_data)?;
+
+    // Verify VAA
+    if accs.vaa.to_chain != CHAIN_ID_SOLANA {
+        return Err(InvalidChain.into());
+    }
+
+    accs.vaa.verify(ctx.program_id)?;
+    accs.vaa.claim(ctx, accs.payer.key)?;
+
+    // Initialize the NFT if it doesn't already exist
+    if !accs.meta.is_initialized() {
+        // Create mint account
+        accs.mint
+            .create(&((&*accs).into()), ctx, accs.payer.key, Exempt);
+
+        // Initialize mint
+        let init_ix = spl_token::instruction::initialize_mint(
+            &spl_token::id(),
+            accs.mint.info().key,
+            accs.mint_authority.key,
+            None,
+            0,
+        )?;
+        invoke_signed(&init_ix, ctx.accounts, &[])?;
+
+        // Create meta account
+        accs.meta
+            .create(&((&*accs).into()), ctx, accs.payer.key, Exempt);
+
+        // Initialize spl meta
+        accs.spl_metadata.verify_derivation(
+            &spl_token_metadata::id(),
+            &SplTokenMetaDerivationData {
+                mint: *accs.mint.info().key,
+            },
+        )?;
+
+        let mut name = accs.vaa.name.clone();
+        name.truncate(32);
+        let mut symbol = accs.vaa.symbol.clone();
+        symbol.truncate(10);
+
+        let spl_token_metadata_ix = spl_token_metadata::instruction::create_metadata_accounts(
+            spl_token_metadata::id(),
+            *accs.spl_metadata.key,
+            *accs.mint.info().key,
+            *accs.mint_authority.info().key,
+            *accs.payer.info().key,
+            *accs.mint_authority.info().key,
+            name,
+            symbol,
+            accs.vaa.uri.clone(),
+            None,
+            0,
+            false,
+            true,
+        );
+        invoke_seeded(&spl_token_metadata_ix, ctx, &accs.mint_authority, None)?;
+
+        // Populate meta account
+        accs.meta.chain = accs.vaa.token_chain;
+        accs.meta.token_address = accs.vaa.token_address;
+        accs.meta.token_id = accs.vaa.token_id.0;
+    }
+
+    if !accs.to.is_initialized() {
+        let associated_addr = spl_associated_token_account::get_associated_token_address(
+            accs.to_authority.info().key,
+            accs.mint.info().key,
+        );
+        if *accs.to_authority.info().key != associated_addr {
+            return Err(InvalidAssociatedAccount.into());
+        }
+        // Create associated token account
+        let ix = spl_associated_token_account::create_associated_token_account(
+            accs.payer.info().key,
+            accs.to_authority.info().key,
+            accs.mint.info().key,
+        );
+        invoke(&ix, ctx.accounts)?;
+    } else if *accs.mint.info().key != accs.to.mint {
+        return Err(InvalidMint.into());
+    }
+
+    // Mint tokens
+    let mint_ix = spl_token::instruction::mint_to(
+        &spl_token::id(),
+        accs.mint.info().key,
+        accs.to.info().key,
+        accs.mint_authority.key,
+        &[],
+        1,
+    )?;
+    invoke_seeded(&mint_ix, ctx, &accs.mint_authority, None)?;
+
+    Ok(())
+}

+ 177 - 0
solana/modules/nft_bridge/program/src/api/governance.rs

@@ -0,0 +1,177 @@
+use crate::{
+    accounts::{
+        ConfigAccount,
+        Endpoint,
+        EndpointDerivationData,
+    },
+    messages::{
+        GovernancePayloadUpgrade,
+        PayloadGovernanceRegisterChain,
+    },
+    types::*,
+    TokenBridgeError::{
+        InvalidChain,
+        InvalidGovernanceKey,
+    },
+};
+use bridge::{
+    vaa::{
+        ClaimableVAA,
+        DeserializePayload,
+        PayloadMessage,
+    },
+    CHAIN_ID_SOLANA,
+};
+use solana_program::{
+    account_info::AccountInfo,
+    program::invoke_signed,
+    program_error::ProgramError,
+    pubkey::Pubkey,
+    sysvar::{
+        clock::Clock,
+        rent::Rent,
+    },
+};
+use solitaire::{
+    processors::seeded::Seeded,
+    CreationLamports::Exempt,
+    *,
+};
+use std::ops::{
+    Deref,
+    DerefMut,
+};
+
+// Confirm that a ClaimableVAA came from the correct chain, signed by the right emitter.
+fn verify_governance<'a, T>(vaa: &ClaimableVAA<'a, T>) -> Result<()>
+where
+    T: DeserializePayload,
+{
+    let expected_emitter = std::env!("EMITTER_ADDRESS");
+    let current_emitter = format!(
+        "{}",
+        Pubkey::new_from_array(vaa.message.meta().emitter_address)
+    );
+    // Fail if the emitter is not the known governance key, or the emitting chain is not Solana.
+    if expected_emitter != current_emitter || vaa.message.meta().emitter_chain != CHAIN_ID_SOLANA {
+        Err(InvalidGovernanceKey.into())
+    } else {
+        Ok(())
+    }
+}
+
+#[derive(FromAccounts)]
+pub struct UpgradeContract<'b> {
+    /// Payer for account creation (vaa-claim)
+    pub payer: Mut<Signer<Info<'b>>>,
+
+    /// GuardianSet change VAA
+    pub vaa: ClaimableVAA<'b, GovernancePayloadUpgrade>,
+
+    /// PDA authority for the loader
+    pub upgrade_authority: Derive<Info<'b>, "upgrade">,
+
+    /// Spill address for the upgrade excess lamports
+    pub spill: Mut<Info<'b>>,
+
+    /// New contract address.
+    pub buffer: Mut<Info<'b>>,
+
+    /// Required by the upgradeable uploader.
+    pub program_data: Mut<Info<'b>>,
+
+    /// Our own address, required by the upgradeable loader.
+    pub own_address: Mut<Info<'b>>,
+
+    // Various sysvar/program accounts needed for the upgradeable loader.
+    pub rent: Sysvar<'b, Rent>,
+    pub clock: Sysvar<'b, Clock>,
+    pub bpf_loader: Info<'b>,
+    pub system: Info<'b>,
+}
+
+impl<'b> InstructionContext<'b> for UpgradeContract<'b> {
+}
+
+#[derive(BorshDeserialize, BorshSerialize, Default)]
+pub struct UpgradeContractData {}
+
+pub fn upgrade_contract(
+    ctx: &ExecutionContext,
+    accs: &mut UpgradeContract,
+    _data: UpgradeContractData,
+) -> Result<()> {
+    verify_governance(&accs.vaa)?;
+    accs.vaa.verify(&ctx.program_id)?;
+
+    accs.vaa.claim(ctx, accs.payer.key)?;
+
+    let upgrade_ix = solana_program::bpf_loader_upgradeable::upgrade(
+        ctx.program_id,
+        &accs.vaa.message.new_contract,
+        accs.upgrade_authority.key,
+        accs.spill.key,
+    );
+
+    let seeds = accs
+        .upgrade_authority
+        .self_bumped_seeds(None, ctx.program_id);
+    let seeds: Vec<&[u8]> = seeds.iter().map(|item| item.as_slice()).collect();
+    let seeds = seeds.as_slice();
+    invoke_signed(&upgrade_ix, ctx.accounts, &[seeds])?;
+
+    Ok(())
+}
+
+#[derive(FromAccounts)]
+pub struct RegisterChain<'b> {
+    pub payer: Mut<Signer<AccountInfo<'b>>>,
+    pub config: ConfigAccount<'b, { AccountState::Initialized }>,
+
+    pub endpoint: Mut<Endpoint<'b, { AccountState::Uninitialized }>>,
+
+    pub vaa: ClaimableVAA<'b, PayloadGovernanceRegisterChain>,
+}
+
+impl<'a> From<&RegisterChain<'a>> for EndpointDerivationData {
+    fn from(accs: &RegisterChain<'a>) -> Self {
+        EndpointDerivationData {
+            emitter_chain: accs.vaa.chain,
+            emitter_address: accs.vaa.endpoint_address,
+        }
+    }
+}
+
+impl<'b> InstructionContext<'b> for RegisterChain<'b> {
+}
+
+#[derive(BorshDeserialize, BorshSerialize, Default)]
+pub struct RegisterChainData {}
+
+pub fn register_chain(
+    ctx: &ExecutionContext,
+    accs: &mut RegisterChain,
+    data: RegisterChainData,
+) -> Result<()> {
+    let derivation_data: EndpointDerivationData = (&*accs).into();
+    accs.endpoint
+        .verify_derivation(ctx.program_id, &derivation_data)?;
+
+    // Claim VAA
+    verify_governance(&accs.vaa)?;
+    accs.vaa.verify(&ctx.program_id)?;
+    accs.vaa.claim(ctx, accs.payer.key)?;
+
+    if accs.vaa.chain == CHAIN_ID_SOLANA {
+        return Err(InvalidChain.into());
+    }
+
+    // Create endpoint
+    accs.endpoint
+        .create(&((&*accs).into()), ctx, accs.payer.key, Exempt);
+
+    accs.endpoint.chain = accs.vaa.chain;
+    accs.endpoint.contract = accs.vaa.endpoint_address;
+
+    Ok(())
+}

+ 44 - 0
solana/modules/nft_bridge/program/src/api/initialize.rs

@@ -0,0 +1,44 @@
+use crate::{
+    accounts::ConfigAccount,
+    types::*,
+};
+use solana_program::{
+    account_info::AccountInfo,
+    msg,
+    program_error::ProgramError,
+    pubkey::Pubkey,
+};
+use solitaire::{
+    CreationLamports::Exempt,
+    *,
+};
+use std::ops::{
+    Deref,
+    DerefMut,
+};
+
+#[derive(FromAccounts)]
+pub struct Initialize<'b> {
+    pub payer: Mut<Signer<AccountInfo<'b>>>,
+
+    pub config: Mut<ConfigAccount<'b, { AccountState::Uninitialized }>>,
+}
+
+#[derive(BorshDeserialize, BorshSerialize, Default)]
+pub struct InitializeData {
+    pub bridge: Pubkey,
+}
+
+impl<'b> InstructionContext<'b> for Initialize<'b> {
+}
+
+pub fn initialize(
+    ctx: &ExecutionContext,
+    accs: &mut Initialize,
+    data: InitializeData,
+) -> Result<()> {
+    // Create the config account
+    accs.config.create(ctx, accs.payer.key, Exempt)?;
+    accs.config.wormhole_bridge = data.bridge;
+    Ok(())
+}

+ 404 - 0
solana/modules/nft_bridge/program/src/api/transfer.rs

@@ -0,0 +1,404 @@
+use crate::{
+    accounts::{
+        AuthoritySigner,
+        ConfigAccount,
+        CoreBridge,
+        CustodyAccount,
+        CustodyAccountDerivationData,
+        CustodySigner,
+        EmitterAccount,
+        MintSigner,
+        SplTokenMeta,
+        SplTokenMetaDerivationData,
+        WrappedDerivationData,
+        WrappedMetaDerivationData,
+        WrappedMint,
+        WrappedTokenMeta,
+    },
+    messages::PayloadTransfer,
+    types::*,
+    TokenBridgeError,
+    TokenBridgeError::{
+        InvalidMetadata,
+        TokenNotNFT,
+        WrongAccountOwner,
+    },
+};
+use bridge::{
+    accounts::Bridge,
+    api::{
+        PostMessage,
+        PostMessageData,
+    },
+    types::ConsistencyLevel,
+    vaa::SerializePayload,
+};
+use primitive_types::U256;
+use solana_program::{
+    account_info::AccountInfo,
+    instruction::{
+        AccountMeta,
+        Instruction,
+    },
+    program::{
+        invoke,
+        invoke_signed,
+    },
+    program_error::ProgramError,
+    program_option::COption,
+    pubkey::Pubkey,
+    sysvar::clock::Clock,
+};
+use solitaire::{
+    processors::seeded::{
+        invoke_seeded,
+        Seeded,
+    },
+    CreationLamports::Exempt,
+    *,
+};
+use spl_token::{
+    error::TokenError::OwnerMismatch,
+    state::{
+        Account,
+        Mint,
+    },
+};
+use spl_token_metadata::state::Metadata;
+use std::ops::{
+    Deref,
+    DerefMut,
+};
+
+#[derive(FromAccounts)]
+pub struct TransferNative<'b> {
+    pub payer: Mut<Signer<AccountInfo<'b>>>,
+
+    pub config: ConfigAccount<'b, { AccountState::Initialized }>,
+
+    pub from: Mut<Data<'b, SplAccount, { AccountState::Initialized }>>,
+
+    pub mint: Mut<Data<'b, SplMint, { AccountState::Initialized }>>,
+    /// SPL Metadata for the associated Mint
+    pub spl_metadata: SplTokenMeta<'b>,
+
+    pub custody: Mut<CustodyAccount<'b, { AccountState::MaybeInitialized }>>,
+
+    // This could allow someone to race someone else's tx if they do the approval in a separate tx.
+    // Therefore the approval must be set in the same tx.
+    pub authority_signer: AuthoritySigner<'b>,
+
+    pub custody_signer: CustodySigner<'b>,
+
+    /// CPI Context
+    pub bridge: Mut<CoreBridge<'b, { AccountState::Initialized }>>,
+
+    /// Account to store the posted message
+    pub message: Signer<Mut<Info<'b>>>,
+
+    /// Emitter of the VAA
+    pub emitter: EmitterAccount<'b>,
+
+    /// Tracker for the emitter sequence
+    pub sequence: Mut<Info<'b>>,
+
+    /// Account to collect tx fee
+    pub fee_collector: Mut<Info<'b>>,
+
+    pub clock: Sysvar<'b, Clock>,
+}
+
+impl<'a> From<&TransferNative<'a>> for CustodyAccountDerivationData {
+    fn from(accs: &TransferNative<'a>) -> Self {
+        CustodyAccountDerivationData {
+            mint: *accs.mint.info().key,
+        }
+    }
+}
+
+impl<'a> From<&TransferNative<'a>> for SplTokenMetaDerivationData {
+    fn from(accs: &TransferNative<'a>) -> Self {
+        SplTokenMetaDerivationData {
+            mint: *accs.mint.info().key,
+        }
+    }
+}
+
+impl<'b> InstructionContext<'b> for TransferNative<'b> {
+}
+
+#[derive(BorshDeserialize, BorshSerialize, Default)]
+pub struct TransferNativeData {
+    pub nonce: u32,
+    pub target_address: Address,
+    pub target_chain: ChainID,
+}
+
+pub fn transfer_native(
+    ctx: &ExecutionContext,
+    accs: &mut TransferNative,
+    data: TransferNativeData,
+) -> Result<()> {
+    // Verify that the custody account is derived correctly
+    let derivation_data: CustodyAccountDerivationData = (&*accs).into();
+    accs.custody
+        .verify_derivation(ctx.program_id, &derivation_data)?;
+
+    let derivation_data: SplTokenMetaDerivationData = (&*accs).into();
+    accs.spl_metadata
+        .verify_derivation(&spl_token_metadata::id(), &derivation_data)?;
+
+    // Verify mints
+    if accs.from.mint != *accs.mint.info().key {
+        return Err(TokenBridgeError::InvalidMint.into());
+    }
+
+    // Token must have metadata
+    if !accs.spl_metadata.data_is_empty() {
+        return Err(TokenNotNFT.into());
+    }
+
+    if *accs.spl_metadata.owner != spl_token_metadata::id() {
+        return Err(WrongAccountOwner.into());
+    }
+
+    // Verify that the token is not a wrapped token
+    if let COption::Some(mint_authority) = accs.mint.mint_authority {
+        if mint_authority == MintSigner::key(None, ctx.program_id) {
+            return Err(TokenBridgeError::TokenNotNative.into());
+        }
+    }
+
+    if !accs.custody.is_initialized() {
+        accs.custody
+            .create(&(&*accs).into(), ctx, accs.payer.key, Exempt)?;
+
+        let init_ix = spl_token::instruction::initialize_account(
+            &spl_token::id(),
+            accs.custody.info().key,
+            accs.mint.info().key,
+            accs.custody_signer.key,
+        )?;
+        invoke_signed(&init_ix, ctx.accounts, &[])?;
+    }
+
+    // Transfer tokens
+    let transfer_ix = spl_token::instruction::transfer(
+        &spl_token::id(),
+        accs.from.info().key,
+        accs.custody.info().key,
+        accs.authority_signer.key,
+        &[],
+        1,
+    )?;
+    invoke_seeded(&transfer_ix, ctx, &accs.authority_signer, None)?;
+
+    // Pay fee
+    let transfer_ix = solana_program::system_instruction::transfer(
+        accs.payer.key,
+        accs.fee_collector.key,
+        accs.bridge.config.fee,
+    );
+    invoke(&transfer_ix, ctx.accounts)?;
+
+    let metadata: Metadata =
+        Metadata::from_account_info(accs.spl_metadata.info()).ok_or(InvalidMetadata)?;
+
+    // Post message
+    let payload = PayloadTransfer {
+        token_address: accs.mint.info().key.to_bytes(),
+        token_chain: 1,
+        to: data.target_address,
+        to_chain: data.target_chain,
+        symbol: metadata.data.symbol,
+        name: metadata.data.name,
+        uri: metadata.data.uri,
+        token_id: U256::from(0), // TODO
+    };
+    let params = (
+        bridge::instruction::Instruction::PostMessage,
+        PostMessageData {
+            nonce: data.nonce,
+            payload: payload.try_to_vec()?,
+            consistency_level: ConsistencyLevel::Confirmed,
+        },
+    );
+
+    let ix = Instruction::new_with_bytes(
+        accs.config.wormhole_bridge,
+        params.try_to_vec()?.as_slice(),
+        vec![
+            AccountMeta::new(*accs.bridge.info().key, false),
+            AccountMeta::new(*accs.message.key, true),
+            AccountMeta::new_readonly(*accs.emitter.key, true),
+            AccountMeta::new(*accs.sequence.key, false),
+            AccountMeta::new(*accs.payer.key, true),
+            AccountMeta::new(*accs.fee_collector.key, false),
+            AccountMeta::new_readonly(*accs.clock.info().key, false),
+            AccountMeta::new_readonly(solana_program::system_program::id(), false),
+            AccountMeta::new_readonly(solana_program::sysvar::rent::ID, false),
+        ],
+    );
+    invoke_seeded(&ix, ctx, &accs.emitter, None)?;
+
+    Ok(())
+}
+
+#[derive(FromAccounts)]
+pub struct TransferWrapped<'b> {
+    pub payer: Mut<Signer<AccountInfo<'b>>>,
+    pub config: ConfigAccount<'b, { AccountState::Initialized }>,
+
+    pub from: Mut<Data<'b, SplAccount, { AccountState::Initialized }>>,
+    pub from_owner: MaybeMut<Signer<Info<'b>>>,
+    pub mint: Mut<WrappedMint<'b, { AccountState::Initialized }>>,
+    pub wrapped_meta: WrappedTokenMeta<'b, { AccountState::Initialized }>,
+    /// SPL Metadata for the associated Mint
+    pub spl_metadata: SplTokenMeta<'b>,
+
+    pub authority_signer: AuthoritySigner<'b>,
+
+    /// CPI Context
+    pub bridge: Mut<CoreBridge<'b, { AccountState::Initialized }>>,
+
+    /// Account to store the posted message
+    pub message: Signer<Mut<Info<'b>>>,
+
+    /// Emitter of the VAA
+    pub emitter: EmitterAccount<'b>,
+
+    /// Tracker for the emitter sequence
+    pub sequence: Mut<Info<'b>>,
+
+    /// Account to collect tx fee
+    pub fee_collector: Mut<Info<'b>>,
+
+    pub clock: Sysvar<'b, Clock>,
+}
+
+impl<'a> From<&TransferWrapped<'a>> for WrappedMetaDerivationData {
+    fn from(accs: &TransferWrapped<'a>) -> Self {
+        WrappedMetaDerivationData {
+            mint_key: *accs.mint.info().key,
+        }
+    }
+}
+
+impl<'a> From<&TransferWrapped<'a>> for SplTokenMetaDerivationData {
+    fn from(accs: &TransferWrapped<'a>) -> Self {
+        SplTokenMetaDerivationData {
+            mint: *accs.mint.info().key,
+        }
+    }
+}
+
+impl<'b> InstructionContext<'b> for TransferWrapped<'b> {
+}
+
+#[derive(BorshDeserialize, BorshSerialize, Default)]
+pub struct TransferWrappedData {
+    pub nonce: u32,
+    pub target_address: Address,
+    pub target_chain: ChainID,
+}
+
+pub fn transfer_wrapped(
+    ctx: &ExecutionContext,
+    accs: &mut TransferWrapped,
+    data: TransferWrappedData,
+) -> Result<()> {
+    // Verify that the from account is owned by the from_owner
+    if &accs.from.owner != accs.from_owner.key {
+        return Err(WrongAccountOwner.into());
+    }
+
+    // Verify mints
+    if accs.mint.info().key != &accs.from.mint {
+        return Err(TokenBridgeError::InvalidMint.into());
+    }
+
+    // Verify that meta is correct
+    let derivation_data: WrappedMetaDerivationData = (&*accs).into();
+    accs.wrapped_meta
+        .verify_derivation(ctx.program_id, &derivation_data)?;
+
+    // Burn tokens
+    let burn_ix = spl_token::instruction::burn(
+        &spl_token::id(),
+        accs.from.info().key,
+        accs.mint.info().key,
+        accs.authority_signer.key,
+        &[],
+        1,
+    )?;
+    invoke_seeded(&burn_ix, ctx, &accs.authority_signer, None)?;
+
+    // Pay fee
+    let transfer_ix = solana_program::system_instruction::transfer(
+        accs.payer.key,
+        accs.fee_collector.key,
+        accs.bridge.config.fee,
+    );
+
+    invoke(&transfer_ix, ctx.accounts)?;
+
+    // Enfoce wrapped meta to be uninitialized.
+    let derivation_data: WrappedMetaDerivationData = (&*accs).into();
+    accs.wrapped_meta
+        .verify_derivation(ctx.program_id, &derivation_data)?;
+
+    // Token must have metadata
+    if !accs.spl_metadata.data_is_empty() {
+        return Err(TokenNotNFT.into());
+    }
+
+    let derivation_data: SplTokenMetaDerivationData = (&*accs).into();
+    accs.spl_metadata
+        .verify_derivation(&spl_token_metadata::id(), &derivation_data)?;
+
+    if *accs.spl_metadata.owner != spl_token_metadata::id() {
+        return Err(WrongAccountOwner.into());
+    }
+
+    let metadata: Metadata =
+        Metadata::from_account_info(accs.spl_metadata.info()).ok_or(InvalidMetadata)?;
+
+    // Post message
+    let payload = PayloadTransfer {
+        token_address: accs.wrapped_meta.token_address,
+        token_chain: accs.wrapped_meta.chain,
+        token_id: U256(accs.wrapped_meta.token_id),
+        to: data.target_address,
+        to_chain: data.target_chain,
+        symbol: metadata.data.symbol,
+        name: metadata.data.name,
+        uri: metadata.data.uri,
+    };
+    let params = (
+        bridge::instruction::Instruction::PostMessage,
+        PostMessageData {
+            nonce: data.nonce,
+            payload: payload.try_to_vec()?,
+            consistency_level: ConsistencyLevel::Confirmed,
+        },
+    );
+
+    let ix = Instruction::new_with_bytes(
+        accs.config.wormhole_bridge,
+        params.try_to_vec()?.as_slice(),
+        vec![
+            AccountMeta::new(*accs.bridge.info().key, false),
+            AccountMeta::new(*accs.message.key, true),
+            AccountMeta::new_readonly(*accs.emitter.key, true),
+            AccountMeta::new(*accs.sequence.key, false),
+            AccountMeta::new(*accs.payer.key, true),
+            AccountMeta::new(*accs.fee_collector.key, false),
+            AccountMeta::new_readonly(*accs.clock.info().key, false),
+            AccountMeta::new_readonly(solana_program::system_program::id(), false),
+            AccountMeta::new_readonly(solana_program::sysvar::rent::ID, false),
+        ],
+    );
+    invoke_seeded(&ix, ctx, &accs.emitter, None)?;
+
+    Ok(())
+}

+ 462 - 0
solana/modules/nft_bridge/program/src/instructions.rs

@@ -0,0 +1,462 @@
+use crate::{
+    accounts::{
+        AuthoritySigner,
+        ConfigAccount,
+        CustodyAccount,
+        CustodyAccountDerivationData,
+        CustodySigner,
+        EmitterAccount,
+        Endpoint,
+        EndpointDerivationData,
+        MintSigner,
+        SplTokenMeta,
+        SplTokenMetaDerivationData,
+        WrappedDerivationData,
+        WrappedMetaDerivationData,
+        WrappedMint,
+        WrappedTokenMeta,
+    },
+    api::{
+        complete_transfer::{
+            CompleteNativeData,
+            CompleteWrappedData,
+        },
+        RegisterChainData,
+        TransferNativeData,
+        TransferWrappedData,
+        UpgradeContractData,
+    },
+    messages::{
+        PayloadGovernanceRegisterChain,
+        PayloadTransfer,
+    },
+};
+use borsh::BorshSerialize;
+use bridge::{
+    accounts::{
+        Bridge,
+        Claim,
+        ClaimDerivationData,
+        FeeCollector,
+        PostedVAA,
+        PostedVAADerivationData,
+        Sequence,
+        SequenceDerivationData,
+    },
+    api::ForeignAddress,
+    instructions::hash_vaa,
+    types::{
+        BridgeConfig,
+        PostedVAAData,
+    },
+    vaa::{
+        ClaimableVAA,
+        PayloadMessage,
+        SerializePayload,
+    },
+    PostVAA,
+    PostVAAData,
+    CHAIN_ID_SOLANA,
+};
+use primitive_types::U256;
+use solana_program::{
+    instruction::{
+        AccountMeta,
+        Instruction,
+    },
+    pubkey::Pubkey,
+};
+use solitaire::{
+    processors::seeded::Seeded,
+    AccountState,
+};
+use spl_token::state::Mint;
+use std::str::FromStr;
+
+pub fn initialize(
+    program_id: Pubkey,
+    payer: Pubkey,
+    bridge: Pubkey,
+) -> solitaire::Result<Instruction> {
+    let config_key = ConfigAccount::<'_, { AccountState::Uninitialized }>::key(None, &program_id);
+    Ok(Instruction {
+        program_id,
+        accounts: vec![
+            AccountMeta::new(payer, true),
+            AccountMeta::new(config_key, false),
+            // Dependencies
+            AccountMeta::new(solana_program::sysvar::rent::id(), false),
+            AccountMeta::new(solana_program::system_program::id(), false),
+        ],
+        data: (crate::instruction::Instruction::Initialize, bridge).try_to_vec()?,
+    })
+}
+
+pub fn complete_native(
+    program_id: Pubkey,
+    bridge_id: Pubkey,
+    payer: Pubkey,
+    message_key: Pubkey,
+    vaa: PostVAAData,
+    to_authority: Pubkey,
+    mint: Pubkey,
+    data: CompleteNativeData,
+) -> solitaire::Result<Instruction> {
+    let config_key = ConfigAccount::<'_, { AccountState::Uninitialized }>::key(None, &program_id);
+    let (message_acc, claim_acc) = claimable_vaa(program_id, message_key, vaa.clone());
+    let endpoint = Endpoint::<'_, { AccountState::Initialized }>::key(
+        &EndpointDerivationData {
+            emitter_chain: vaa.emitter_chain,
+            emitter_address: vaa.emitter_address,
+        },
+        &program_id,
+    );
+    let custody_key = CustodyAccount::<'_, { AccountState::Initialized }>::key(
+        &CustodyAccountDerivationData { mint },
+        &program_id,
+    );
+    let custody_signer_key = CustodySigner::key(None, &program_id);
+    let associated_addr =
+        spl_associated_token_account::get_associated_token_address(&to_authority, &mint);
+
+    Ok(Instruction {
+        program_id,
+        accounts: vec![
+            AccountMeta::new(payer, true),
+            AccountMeta::new_readonly(config_key, false),
+            message_acc,
+            claim_acc,
+            AccountMeta::new_readonly(endpoint, false),
+            AccountMeta::new(associated_addr, false),
+            AccountMeta::new_readonly(to_authority, false),
+            AccountMeta::new(custody_key, false),
+            AccountMeta::new_readonly(mint, false),
+            AccountMeta::new_readonly(custody_signer_key, false),
+            // Dependencies
+            AccountMeta::new_readonly(solana_program::sysvar::rent::id(), false),
+            AccountMeta::new_readonly(solana_program::system_program::id(), false),
+            // Program
+            AccountMeta::new_readonly(bridge_id, false),
+            AccountMeta::new_readonly(spl_token::id(), false),
+            AccountMeta::new_readonly(spl_associated_token_account::id(), false),
+        ],
+        data: (crate::instruction::Instruction::CompleteNative, data).try_to_vec()?,
+    })
+}
+
+pub fn complete_wrapped(
+    program_id: Pubkey,
+    bridge_id: Pubkey,
+    payer: Pubkey,
+    message_key: Pubkey,
+    vaa: PostVAAData,
+    payload: PayloadTransfer,
+    to_authority: Pubkey,
+    data: CompleteWrappedData,
+) -> solitaire::Result<Instruction> {
+    let config_key = ConfigAccount::<'_, { AccountState::Uninitialized }>::key(None, &program_id);
+    let (message_acc, claim_acc) = claimable_vaa(program_id, message_key, vaa.clone());
+    let endpoint = Endpoint::<'_, { AccountState::Initialized }>::key(
+        &EndpointDerivationData {
+            emitter_chain: vaa.emitter_chain,
+            emitter_address: vaa.emitter_address,
+        },
+        &program_id,
+    );
+    let mint_key = WrappedMint::<'_, { AccountState::Uninitialized }>::key(
+        &WrappedDerivationData {
+            token_chain: payload.token_chain,
+            token_address: payload.token_address,
+            token_id: payload.token_id,
+        },
+        &program_id,
+    );
+    let mint_authority_key = MintSigner::key(None, &program_id);
+
+    let mint_meta_key = WrappedTokenMeta::<'_, { AccountState::Uninitialized }>::key(
+        &WrappedMetaDerivationData { mint_key },
+        &program_id,
+    );
+    // SPL Metadata
+    let spl_metadata = SplTokenMeta::key(
+        &SplTokenMetaDerivationData { mint: mint_key },
+        &spl_token_metadata::id(),
+    );
+    let associated_addr =
+        spl_associated_token_account::get_associated_token_address(&to_authority, &mint_key);
+
+    Ok(Instruction {
+        program_id,
+        accounts: vec![
+            AccountMeta::new(payer, true),
+            AccountMeta::new_readonly(config_key, false),
+            message_acc,
+            claim_acc,
+            AccountMeta::new_readonly(endpoint, false),
+            AccountMeta::new(associated_addr, false),
+            AccountMeta::new_readonly(to_authority, false),
+            AccountMeta::new(mint_key, false),
+            AccountMeta::new(mint_meta_key, false),
+            AccountMeta::new(spl_metadata, false),
+            AccountMeta::new_readonly(mint_authority_key, false),
+            // Dependencies
+            AccountMeta::new_readonly(solana_program::sysvar::rent::id(), false),
+            AccountMeta::new_readonly(solana_program::system_program::id(), false),
+            // Program
+            AccountMeta::new_readonly(bridge_id, false),
+            AccountMeta::new_readonly(spl_token::id(), false),
+            AccountMeta::new_readonly(spl_associated_token_account::id(), false),
+        ],
+        data: (crate::instruction::Instruction::CompleteWrapped, data).try_to_vec()?,
+    })
+}
+
+pub fn register_chain(
+    program_id: Pubkey,
+    bridge_id: Pubkey,
+    payer: Pubkey,
+    message_key: Pubkey,
+    vaa: PostVAAData,
+    payload: PayloadGovernanceRegisterChain,
+    data: RegisterChainData,
+) -> solitaire::Result<Instruction> {
+    let config_key = ConfigAccount::<'_, { AccountState::Uninitialized }>::key(None, &program_id);
+    let (message_acc, claim_acc) = claimable_vaa(program_id, message_key, vaa);
+    let endpoint = Endpoint::<'_, { AccountState::Initialized }>::key(
+        &EndpointDerivationData {
+            emitter_chain: payload.chain,
+            emitter_address: payload.endpoint_address,
+        },
+        &program_id,
+    );
+
+    Ok(Instruction {
+        program_id,
+        accounts: vec![
+            AccountMeta::new(payer, true),
+            AccountMeta::new_readonly(config_key, false),
+            AccountMeta::new(endpoint, false),
+            message_acc,
+            claim_acc,
+            // Dependencies
+            AccountMeta::new(solana_program::sysvar::rent::id(), false),
+            AccountMeta::new(solana_program::system_program::id(), false),
+            // Program
+            AccountMeta::new_readonly(bridge_id, false),
+        ],
+        data: (crate::instruction::Instruction::RegisterChain, data).try_to_vec()?,
+    })
+}
+
+fn claimable_vaa(
+    bridge_id: Pubkey,
+    message_key: Pubkey,
+    vaa: PostVAAData,
+) -> (AccountMeta, AccountMeta) {
+    let claim_key = Claim::<'_, { AccountState::Initialized }>::key(
+        &ClaimDerivationData {
+            emitter_address: vaa.emitter_address,
+            emitter_chain: vaa.emitter_chain,
+            sequence: vaa.sequence,
+        },
+        &bridge_id,
+    );
+
+    (
+        AccountMeta::new_readonly(message_key, false),
+        AccountMeta::new(claim_key, false),
+    )
+}
+
+pub fn transfer_native(
+    program_id: Pubkey,
+    bridge_id: Pubkey,
+    payer: Pubkey,
+    message_key: Pubkey,
+    from: Pubkey,
+    mint: Pubkey,
+    data: TransferNativeData,
+) -> solitaire::Result<Instruction> {
+    let config_key = ConfigAccount::<'_, { AccountState::Uninitialized }>::key(None, &program_id);
+    let custody_key = CustodyAccount::<'_, { AccountState::Initialized }>::key(
+        &CustodyAccountDerivationData { mint },
+        &program_id,
+    );
+
+    let authority_signer_key = AuthoritySigner::key(None, &program_id);
+    let custody_signer_key = CustodySigner::key(None, &program_id);
+    let emitter_key = EmitterAccount::key(None, &program_id);
+
+    // SPL Metadata
+    let spl_metadata = SplTokenMeta::key(
+        &SplTokenMetaDerivationData { mint: mint },
+        &spl_token_metadata::id(),
+    );
+
+    // Bridge keys
+    let bridge_config = Bridge::<'_, { AccountState::Uninitialized }>::key(None, &bridge_id);
+    let sequence_key = Sequence::key(
+        &SequenceDerivationData {
+            emitter_key: &emitter_key,
+        },
+        &bridge_id,
+    );
+    let fee_collector_key = FeeCollector::key(None, &bridge_id);
+
+    Ok(Instruction {
+        program_id,
+        accounts: vec![
+            AccountMeta::new(payer, true),
+            AccountMeta::new_readonly(config_key, false),
+            AccountMeta::new(from, false),
+            AccountMeta::new(mint, false),
+            AccountMeta::new_readonly(spl_metadata, false),
+            AccountMeta::new(custody_key, false),
+            AccountMeta::new_readonly(authority_signer_key, false),
+            AccountMeta::new_readonly(custody_signer_key, false),
+            AccountMeta::new(bridge_config, false),
+            AccountMeta::new(message_key, true),
+            AccountMeta::new_readonly(emitter_key, false),
+            AccountMeta::new(sequence_key, false),
+            AccountMeta::new(fee_collector_key, false),
+            AccountMeta::new_readonly(solana_program::sysvar::clock::id(), false),
+            // Dependencies
+            AccountMeta::new_readonly(solana_program::sysvar::rent::id(), false),
+            AccountMeta::new_readonly(solana_program::system_program::id(), false),
+            // Program
+            AccountMeta::new_readonly(bridge_id, false),
+            AccountMeta::new_readonly(spl_token::id(), false),
+        ],
+        data: (crate::instruction::Instruction::TransferNative, data).try_to_vec()?,
+    })
+}
+
+pub fn transfer_wrapped(
+    program_id: Pubkey,
+    bridge_id: Pubkey,
+    payer: Pubkey,
+    message_key: Pubkey,
+    from: Pubkey,
+    from_owner: Pubkey,
+    token_chain: u16,
+    token_address: ForeignAddress,
+    token_id: U256,
+    data: TransferWrappedData,
+) -> solitaire::Result<Instruction> {
+    let config_key = ConfigAccount::<'_, { AccountState::Uninitialized }>::key(None, &program_id);
+
+    let wrapped_mint_key = WrappedMint::<'_, { AccountState::Uninitialized }>::key(
+        &WrappedDerivationData {
+            token_chain,
+            token_address,
+            token_id,
+        },
+        &program_id,
+    );
+    let wrapped_meta_key = WrappedTokenMeta::<'_, { AccountState::Uninitialized }>::key(
+        &WrappedMetaDerivationData {
+            mint_key: wrapped_mint_key,
+        },
+        &program_id,
+    );
+
+    let authority_signer = AuthoritySigner::key(None, &program_id);
+    let emitter_key = EmitterAccount::key(None, &program_id);
+
+    // SPL Metadata
+    let spl_metadata = SplTokenMeta::key(
+        &SplTokenMetaDerivationData {
+            mint: wrapped_mint_key,
+        },
+        &spl_token_metadata::id(),
+    );
+
+    // Bridge keys
+    let bridge_config = Bridge::<'_, { AccountState::Uninitialized }>::key(None, &bridge_id);
+    let sequence_key = Sequence::key(
+        &SequenceDerivationData {
+            emitter_key: &emitter_key,
+        },
+        &bridge_id,
+    );
+    let fee_collector_key = FeeCollector::key(None, &bridge_id);
+
+    Ok(Instruction {
+        program_id,
+        accounts: vec![
+            AccountMeta::new(payer, true),
+            AccountMeta::new_readonly(config_key, false),
+            AccountMeta::new(from, false),
+            AccountMeta::new_readonly(from_owner, true),
+            AccountMeta::new(wrapped_mint_key, false),
+            AccountMeta::new_readonly(wrapped_meta_key, false),
+            AccountMeta::new_readonly(spl_metadata, false),
+            AccountMeta::new_readonly(authority_signer, false),
+            AccountMeta::new(bridge_config, false),
+            AccountMeta::new(message_key, true),
+            AccountMeta::new_readonly(emitter_key, false),
+            AccountMeta::new(sequence_key, false),
+            AccountMeta::new(fee_collector_key, false),
+            AccountMeta::new_readonly(solana_program::sysvar::clock::id(), false),
+            // Dependencies
+            AccountMeta::new_readonly(solana_program::sysvar::rent::id(), false),
+            AccountMeta::new_readonly(solana_program::system_program::id(), false),
+            // Program
+            AccountMeta::new_readonly(bridge_id, false),
+            AccountMeta::new_readonly(spl_token::id(), false),
+        ],
+        data: (crate::instruction::Instruction::TransferWrapped, data).try_to_vec()?,
+    })
+}
+
+pub fn upgrade_contract(
+    program_id: Pubkey,
+    payer: Pubkey,
+    payload_message: Pubkey,
+    emitter: Pubkey,
+    new_contract: Pubkey,
+    spill: Pubkey,
+    sequence: u64,
+) -> Instruction {
+    let claim = Claim::<'_, { AccountState::Uninitialized }>::key(
+        &ClaimDerivationData {
+            emitter_address: emitter.to_bytes(),
+            emitter_chain: CHAIN_ID_SOLANA,
+            sequence,
+        },
+        &program_id,
+    );
+
+    let (upgrade_authority, _) = Pubkey::find_program_address(&["upgrade".as_bytes()], &program_id);
+
+    let (program_data, _) = Pubkey::find_program_address(
+        &[program_id.as_ref()],
+        &solana_program::bpf_loader_upgradeable::id(),
+    );
+
+    Instruction {
+        program_id,
+
+        accounts: vec![
+            AccountMeta::new(payer, true),
+            AccountMeta::new_readonly(payload_message, false),
+            AccountMeta::new(claim, false),
+            AccountMeta::new_readonly(upgrade_authority, false),
+            AccountMeta::new(spill, false),
+            AccountMeta::new(new_contract, false),
+            AccountMeta::new(program_data, false),
+            AccountMeta::new(program_id, false),
+            AccountMeta::new_readonly(solana_program::sysvar::rent::id(), false),
+            AccountMeta::new_readonly(solana_program::sysvar::clock::id(), false),
+            AccountMeta::new_readonly(solana_program::bpf_loader_upgradeable::id(), false),
+            AccountMeta::new_readonly(solana_program::system_program::id(), false),
+        ],
+
+        data: (
+            crate::instruction::Instruction::UpgradeContract,
+            UpgradeContractData {},
+        )
+            .try_to_vec()
+            .unwrap(),
+    }
+}

+ 78 - 0
solana/modules/nft_bridge/program/src/lib.rs

@@ -0,0 +1,78 @@
+#![feature(const_generics)]
+#![allow(warnings)]
+
+// #![cfg(all(target_arch = "bpf", not(feature = "no-entrypoint")))]
+
+#[cfg(feature = "no-entrypoint")]
+pub mod instructions;
+
+#[cfg(feature = "wasm")]
+#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
+extern crate wasm_bindgen;
+
+#[cfg(feature = "wasm")]
+#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
+pub mod wasm;
+
+pub mod accounts;
+pub mod api;
+pub mod messages;
+pub mod types;
+
+pub use api::{
+    complete_native,
+    complete_wrapped,
+    initialize,
+    register_chain,
+    transfer_native,
+    transfer_wrapped,
+    upgrade_contract,
+    CompleteNative,
+    CompleteNativeData,
+    CompleteWrapped,
+    CompleteWrappedData,
+    Initialize,
+    InitializeData,
+    RegisterChain,
+    RegisterChainData,
+    TransferNative,
+    TransferNativeData,
+    TransferWrapped,
+    TransferWrappedData,
+    UpgradeContract,
+    UpgradeContractData,
+};
+
+use solitaire::*;
+use std::error::Error;
+
+pub enum TokenBridgeError {
+    AlreadyExecuted,
+    InvalidChain,
+    InvalidGovernanceKey,
+    InvalidMetadata,
+    InvalidMint,
+    InvalidPayload,
+    InvalidUTF8String,
+    TokenNotNative,
+    UninitializedMint,
+    WrongAccountOwner,
+    TokenNotNFT,
+    InvalidAssociatedAccount,
+}
+
+impl From<TokenBridgeError> for SolitaireError {
+    fn from(t: TokenBridgeError) -> SolitaireError {
+        SolitaireError::Custom(t as u64)
+    }
+}
+
+solitaire! {
+    Initialize(InitializeData) => initialize,
+    CompleteNative(CompleteNativeData) => complete_native,
+    CompleteWrapped(CompleteWrappedData) => complete_wrapped,
+    TransferWrapped(TransferWrappedData) => transfer_wrapped,
+    TransferNative(TransferNativeData) => transfer_native,
+    RegisterChain(RegisterChainData) => register_chain,
+    UpgradeContract(UpgradeContractData) => upgrade_contract,
+}

+ 320 - 0
solana/modules/nft_bridge/program/src/messages.rs

@@ -0,0 +1,320 @@
+use crate::{
+    types::{
+        Address,
+        ChainID,
+    },
+    TokenBridgeError,
+};
+use borsh::{
+    BorshDeserialize,
+    BorshSerialize,
+};
+use bridge::{
+    vaa::{
+        DeserializePayload,
+        SerializePayload,
+    },
+    DeserializeGovernancePayload,
+    SerializeGovernancePayload,
+};
+use byteorder::{
+    BigEndian,
+    ReadBytesExt,
+    WriteBytesExt,
+};
+use primitive_types::U256;
+use solana_program::{
+    native_token::Sol,
+    program_error::{
+        ProgramError,
+        ProgramError::InvalidAccountData,
+    },
+    pubkey::Pubkey,
+};
+use solitaire::SolitaireError;
+use std::{
+    error::Error,
+    io::{
+        Cursor,
+        Read,
+        Write,
+    },
+    str::Utf8Error,
+    string::FromUtf8Error,
+};
+
+pub const MODULE: &str = "NFTBridge";
+
+#[derive(PartialEq, Debug, Clone)]
+pub struct PayloadTransfer {
+    // Address of the token. Left-zero-padded if shorter than 32 bytes
+    pub token_address: Address,
+    // Chain ID of the token
+    pub token_chain: ChainID,
+    // Symbol of the token
+    pub symbol: String,
+    // Name of the token
+    pub name: String,
+    // TokenID of the token (big-endian uint256)
+    pub token_id: U256,
+    // URI of the token metadata
+    pub uri: String,
+    // Address of the recipient. Left-zero-padded if shorter than 32 bytes
+    pub to: Address,
+    // Chain ID of the recipient
+    pub to_chain: ChainID,
+}
+
+impl DeserializePayload for PayloadTransfer {
+    fn deserialize(buf: &mut &[u8]) -> Result<Self, SolitaireError> {
+        let mut v = Cursor::new(buf);
+
+        if v.read_u8()? != 1 {
+            return Err(SolitaireError::Custom(0));
+        };
+
+        let mut token_address = Address::default();
+        v.read_exact(&mut token_address)?;
+
+        let token_chain = v.read_u16::<BigEndian>()?;
+
+        let mut symbol_data: [u8; 32] = [0; 32];
+        v.read_exact(&mut symbol_data)?;
+        let mut symbol = String::from_utf8(symbol_data.to_vec())
+            .map_err::<SolitaireError, _>(|_| TokenBridgeError::InvalidUTF8String.into())?;
+        symbol = symbol.chars().filter(|c| c != &'\0').collect();
+
+        let mut name_data: [u8; 32] = [0; 32];
+        v.read_exact(&mut name_data)?;
+        let mut name = String::from_utf8(name_data.to_vec())
+            .map_err::<SolitaireError, _>(|_| TokenBridgeError::InvalidUTF8String.into())?;
+        name = name.chars().filter(|c| c != &'\0').collect();
+
+        let mut id_data: [u8; 32] = [0; 32];
+        v.read_exact(&mut id_data)?;
+        let token_id = U256::from_big_endian(&id_data);
+
+        let uri_len = v.read_u8()?;
+        let mut uri_bytes = vec![0u8; uri_len as usize];
+        v.read_exact(uri_bytes.as_mut_slice())?;
+        let uri = String::from_utf8(uri_bytes).unwrap();
+
+        let mut to = Address::default();
+        v.read_exact(&mut to)?;
+
+        let to_chain = v.read_u16::<BigEndian>()?;
+
+        if v.position() != v.into_inner().len() as u64 {
+            return Err(InvalidAccountData.into());
+        }
+
+        Ok(PayloadTransfer {
+            token_address,
+            token_chain,
+            to,
+            to_chain,
+            symbol,
+            name,
+            token_id,
+            uri,
+        })
+    }
+}
+
+impl SerializePayload for PayloadTransfer {
+    fn serialize<W: Write>(&self, writer: &mut W) -> Result<(), SolitaireError> {
+        // Payload ID
+        writer.write_u8(1)?;
+
+        writer.write(&self.token_address)?;
+        writer.write_u16::<BigEndian>(self.token_chain)?;
+
+        let mut symbol: [u8; 32] = [0; 32];
+        for i in 0..self.symbol.len() {
+            symbol[i] = self.symbol.as_bytes()[i];
+        }
+        writer.write(&symbol);
+
+        let mut name: [u8; 32] = [0; 32];
+        for i in 0..self.name.len() {
+            name[i] = self.name.as_bytes()[i];
+        }
+        writer.write(&name);
+
+        let mut id_data: [u8; 32] = [0; 32];
+        self.token_id.to_big_endian(&mut id_data);
+        writer.write(&id_data)?;
+
+        writer.write_u8(self.uri.len() as u8)?;
+        writer.write(self.uri.as_bytes())?;
+
+        writer.write(&self.to)?;
+        writer.write_u16::<BigEndian>(self.to_chain)?;
+
+        Ok(())
+    }
+}
+
+#[derive(PartialEq, Debug)]
+pub struct PayloadGovernanceRegisterChain {
+    // Chain ID of the chain to be registered
+    pub chain: ChainID,
+    // Address of the endpoint on the chain
+    pub endpoint_address: Address,
+}
+
+impl SerializeGovernancePayload for PayloadGovernanceRegisterChain {
+    const MODULE: &'static str = MODULE;
+    const ACTION: u8 = 1;
+}
+
+impl DeserializeGovernancePayload for PayloadGovernanceRegisterChain {
+}
+
+impl DeserializePayload for PayloadGovernanceRegisterChain
+where
+    Self: DeserializeGovernancePayload,
+{
+    fn deserialize(buf: &mut &[u8]) -> Result<Self, SolitaireError> {
+        let mut v = Cursor::new(buf);
+        Self::check_governance_header(&mut v)?;
+
+        let chain = v.read_u16::<BigEndian>()?;
+        let mut endpoint_address = [0u8; 32];
+        v.read_exact(&mut endpoint_address)?;
+
+        if v.position() != v.into_inner().len() as u64 {
+            return Err(InvalidAccountData.into());
+        }
+
+        Ok(PayloadGovernanceRegisterChain {
+            chain,
+            endpoint_address,
+        })
+    }
+}
+
+impl SerializePayload for PayloadGovernanceRegisterChain
+where
+    Self: SerializeGovernancePayload,
+{
+    fn serialize<W: Write>(&self, writer: &mut W) -> Result<(), SolitaireError> {
+        self.write_governance_header(writer);
+        // Payload ID
+        writer.write_u16::<BigEndian>(self.chain)?;
+        writer.write(&self.endpoint_address[..])?;
+
+        Ok(())
+    }
+}
+
+#[derive(PartialEq, Debug)]
+pub struct GovernancePayloadUpgrade {
+    // Address of the new Implementation
+    pub new_contract: Pubkey,
+}
+
+impl SerializePayload for GovernancePayloadUpgrade {
+    fn serialize<W: Write>(&self, v: &mut W) -> std::result::Result<(), SolitaireError> {
+        self.write_governance_header(v);
+        v.write(&self.new_contract.to_bytes())?;
+        Ok(())
+    }
+}
+
+impl DeserializePayload for GovernancePayloadUpgrade
+where
+    Self: DeserializeGovernancePayload,
+{
+    fn deserialize(buf: &mut &[u8]) -> Result<Self, SolitaireError> {
+        let mut c = Cursor::new(buf);
+        Self::check_governance_header(&mut c)?;
+
+        let mut addr = [0u8; 32];
+        c.read_exact(&mut addr)?;
+
+        if c.position() != c.into_inner().len() as u64 {
+            return Err(InvalidAccountData.into());
+        }
+
+        Ok(GovernancePayloadUpgrade {
+            new_contract: Pubkey::new(&addr[..]),
+        })
+    }
+}
+
+impl SerializeGovernancePayload for GovernancePayloadUpgrade {
+    const MODULE: &'static str = MODULE;
+    const ACTION: u8 = 2;
+}
+
+impl DeserializeGovernancePayload for GovernancePayloadUpgrade {
+}
+
+#[cfg(feature = "no-entrypoint")]
+mod tests {
+    use crate::messages::{
+        GovernancePayloadUpgrade,
+        PayloadGovernanceRegisterChain,
+        PayloadTransfer,
+    };
+    use bridge::{
+        DeserializePayload,
+        SerializePayload,
+    };
+    use primitive_types::U256;
+    use rand::RngCore;
+    use solana_program::pubkey::Pubkey;
+
+    #[test]
+    pub fn test_serde_transfer() {
+        let mut token_address = [0u8; 32];
+        rand::thread_rng().fill_bytes(&mut token_address);
+        let mut to = [0u8; 32];
+        rand::thread_rng().fill_bytes(&mut to);
+
+        let transfer_original = PayloadTransfer {
+            token_address,
+            token_chain: 8,
+            to,
+            to_chain: 1,
+            name: String::from("Token Token"),
+            symbol: String::from("TEST"),
+            uri: String::from("https://abc.abc.abc.com"),
+            token_id: U256::from(1234),
+        };
+
+        let mut data = transfer_original.try_to_vec().unwrap();
+        let transfer_deser = PayloadTransfer::deserialize(&mut data.as_slice()).unwrap();
+
+        assert_eq!(transfer_original, transfer_deser);
+    }
+
+    #[test]
+    pub fn test_serde_gov_upgrade() {
+        let original = GovernancePayloadUpgrade {
+            new_contract: Pubkey::new_unique(),
+        };
+
+        let mut data = original.try_to_vec().unwrap();
+        let deser = GovernancePayloadUpgrade::deserialize(&mut data.as_slice()).unwrap();
+
+        assert_eq!(original, deser);
+    }
+
+    #[test]
+    pub fn test_serde_gov_register_chain() {
+        let mut endpoint_address = [0u8; 32];
+        rand::thread_rng().fill_bytes(&mut endpoint_address);
+
+        let original = PayloadGovernanceRegisterChain {
+            chain: 8,
+            endpoint_address,
+        };
+
+        let mut data = original.try_to_vec().unwrap();
+        let deser = PayloadGovernanceRegisterChain::deserialize(&mut data.as_slice()).unwrap();
+
+        assert_eq!(original, deser);
+    }
+}

+ 64 - 0
solana/modules/nft_bridge/program/src/types.rs

@@ -0,0 +1,64 @@
+use borsh::{
+    BorshDeserialize,
+    BorshSerialize,
+};
+use primitive_types::U256;
+use serde::{
+    Deserialize,
+    Serialize,
+};
+use solana_program::pubkey::Pubkey;
+use solitaire::{
+    pack_type,
+    processors::seeded::{
+        AccountOwner,
+        Owned,
+    },
+};
+use spl_token::state::{
+    Account,
+    Mint,
+};
+use spl_token_metadata::state::Metadata;
+
+pub type Address = [u8; 32];
+pub type ChainID = u16;
+
+#[derive(Default, Clone, Copy, BorshDeserialize, BorshSerialize, Serialize, Deserialize)]
+pub struct Config {
+    pub wormhole_bridge: Pubkey,
+}
+
+impl Owned for Config {
+    fn owner(&self) -> AccountOwner {
+        AccountOwner::This
+    }
+}
+
+#[derive(Default, Clone, Copy, BorshDeserialize, BorshSerialize, Serialize, Deserialize)]
+pub struct EndpointRegistration {
+    pub chain: ChainID,
+    pub contract: Address,
+}
+
+impl Owned for EndpointRegistration {
+    fn owner(&self) -> AccountOwner {
+        AccountOwner::This
+    }
+}
+
+#[derive(Default, Clone, Copy, BorshDeserialize, BorshSerialize, Serialize, Deserialize)]
+pub struct WrappedMeta {
+    pub chain: ChainID,
+    pub token_address: Address,
+    pub token_id: [u64; 4],
+}
+
+impl Owned for WrappedMeta {
+    fn owner(&self) -> AccountOwner {
+        AccountOwner::This
+    }
+}
+
+pack_type!(SplMint, Mint, AccountOwner::Other(spl_token::id()));
+pack_type!(SplAccount, Account, AccountOwner::Other(spl_token::id()));

+ 367 - 0
solana/modules/nft_bridge/program/src/wasm.rs

@@ -0,0 +1,367 @@
+use crate::{
+    accounts::{
+        AuthoritySigner,
+        EmitterAccount,
+        WrappedDerivationData,
+        WrappedMetaDerivationData,
+        WrappedMint,
+        WrappedTokenMeta,
+    },
+    instructions::{
+        complete_native,
+        complete_wrapped,
+        register_chain,
+        transfer_native,
+        transfer_wrapped,
+        upgrade_contract,
+    },
+    messages::{
+        GovernancePayloadUpgrade,
+        PayloadGovernanceRegisterChain,
+        PayloadTransfer,
+    },
+    types::{
+        EndpointRegistration,
+        WrappedMeta,
+    },
+    CompleteNativeData,
+    CompleteWrappedData,
+    RegisterChainData,
+    TransferNativeData,
+    TransferWrappedData,
+};
+use borsh::BorshDeserialize;
+use bridge::{
+    accounts::PostedVAADerivationData,
+    instructions::hash_vaa,
+    vaa::VAA,
+    DeserializePayload,
+    PostVAAData,
+};
+use primitive_types::U256;
+use solana_program::pubkey::Pubkey;
+use solitaire::{
+    processors::seeded::Seeded,
+    AccountState,
+};
+use std::str::FromStr;
+use wasm_bindgen::prelude::*;
+
+#[wasm_bindgen]
+pub fn transfer_native_ix(
+    program_id: String,
+    bridge_id: String,
+    payer: String,
+    message: String,
+    from: String,
+    mint: String,
+    nonce: u32,
+    target_address: Vec<u8>,
+    target_chain: u16,
+) -> JsValue {
+    let program_id = Pubkey::from_str(program_id.as_str()).unwrap();
+    let bridge_id = Pubkey::from_str(bridge_id.as_str()).unwrap();
+    let payer = Pubkey::from_str(payer.as_str()).unwrap();
+    let message = Pubkey::from_str(message.as_str()).unwrap();
+    let from = Pubkey::from_str(from.as_str()).unwrap();
+    let mint = Pubkey::from_str(mint.as_str()).unwrap();
+
+    let mut target_addr = [0u8; 32];
+    target_addr.copy_from_slice(target_address.as_slice());
+
+    let ix = transfer_native(
+        program_id,
+        bridge_id,
+        payer,
+        message,
+        from,
+        mint,
+        TransferNativeData {
+            nonce,
+            target_address: target_addr,
+            target_chain,
+        },
+    )
+    .unwrap();
+
+    JsValue::from_serde(&ix).unwrap()
+}
+
+#[wasm_bindgen]
+pub fn transfer_wrapped_ix(
+    program_id: String,
+    bridge_id: String,
+    payer: String,
+    message: String,
+    from: String,
+    from_owner: String,
+    token_chain: u16,
+    token_address: Vec<u8>,
+    token_id: Vec<u8>,
+    nonce: u32,
+    target_address: Vec<u8>,
+    target_chain: u16,
+) -> JsValue {
+    let program_id = Pubkey::from_str(program_id.as_str()).unwrap();
+    let bridge_id = Pubkey::from_str(bridge_id.as_str()).unwrap();
+    let payer = Pubkey::from_str(payer.as_str()).unwrap();
+    let message = Pubkey::from_str(message.as_str()).unwrap();
+    let from = Pubkey::from_str(from.as_str()).unwrap();
+    let from_owner = Pubkey::from_str(from_owner.as_str()).unwrap();
+
+    let mut target_addr = [0u8; 32];
+    target_addr.copy_from_slice(target_address.as_slice());
+    let mut token_addr = [0u8; 32];
+    token_addr.copy_from_slice(token_address.as_slice());
+    let token_id = U256::from_little_endian(token_id.as_slice());
+
+    let ix = transfer_wrapped(
+        program_id,
+        bridge_id,
+        payer,
+        message,
+        from,
+        from_owner,
+        token_chain,
+        token_addr,
+        token_id,
+        TransferWrappedData {
+            nonce,
+            target_address: target_addr,
+            target_chain,
+        },
+    )
+    .unwrap();
+
+    JsValue::from_serde(&ix).unwrap()
+}
+
+#[wasm_bindgen]
+pub fn complete_transfer_native_ix(
+    program_id: String,
+    bridge_id: String,
+    payer: String,
+    to_authority: String,
+    vaa: Vec<u8>,
+) -> JsValue {
+    let program_id = Pubkey::from_str(program_id.as_str()).unwrap();
+    let bridge_id = Pubkey::from_str(bridge_id.as_str()).unwrap();
+    let payer = Pubkey::from_str(payer.as_str()).unwrap();
+    let to_authority = Pubkey::from_str(to_authority.as_str()).unwrap();
+    let vaa = VAA::deserialize(vaa.as_slice()).unwrap();
+    let payload = PayloadTransfer::deserialize(&mut vaa.payload.as_slice()).unwrap();
+    let message_key = bridge::accounts::PostedVAA::<'_, { AccountState::Uninitialized }>::key(
+        &PostedVAADerivationData {
+            payload_hash: hash_vaa(&vaa.clone().into()).to_vec(),
+        },
+        &bridge_id,
+    );
+    let post_vaa_data = PostVAAData {
+        version: vaa.version,
+        guardian_set_index: vaa.guardian_set_index,
+        timestamp: vaa.timestamp,
+        nonce: vaa.nonce,
+        emitter_chain: vaa.emitter_chain,
+        emitter_address: vaa.emitter_address,
+        sequence: vaa.sequence,
+        consistency_level: vaa.consistency_level,
+        payload: vaa.payload,
+    };
+
+    let ix = complete_native(
+        program_id,
+        bridge_id,
+        payer,
+        message_key,
+        post_vaa_data,
+        to_authority,
+        Pubkey::new(&payload.token_address),
+        CompleteNativeData {},
+    )
+    .unwrap();
+
+    JsValue::from_serde(&ix).unwrap()
+}
+
+#[wasm_bindgen]
+pub fn complete_transfer_wrapped_ix(
+    program_id: String,
+    bridge_id: String,
+    payer: String,
+    to_authority: String,
+    vaa: Vec<u8>,
+) -> JsValue {
+    let program_id = Pubkey::from_str(program_id.as_str()).unwrap();
+    let bridge_id = Pubkey::from_str(bridge_id.as_str()).unwrap();
+    let payer = Pubkey::from_str(payer.as_str()).unwrap();
+    let to_authority = Pubkey::from_str(to_authority.as_str()).unwrap();
+    let vaa = VAA::deserialize(vaa.as_slice()).unwrap();
+    let payload = PayloadTransfer::deserialize(&mut vaa.payload.as_slice()).unwrap();
+    let message_key = bridge::accounts::PostedVAA::<'_, { AccountState::Uninitialized }>::key(
+        &PostedVAADerivationData {
+            payload_hash: hash_vaa(&vaa.clone().into()).to_vec(),
+        },
+        &bridge_id,
+    );
+    let post_vaa_data = PostVAAData {
+        version: vaa.version,
+        guardian_set_index: vaa.guardian_set_index,
+        timestamp: vaa.timestamp,
+        nonce: vaa.nonce,
+        emitter_chain: vaa.emitter_chain,
+        emitter_address: vaa.emitter_address,
+        sequence: vaa.sequence,
+        consistency_level: vaa.consistency_level,
+        payload: vaa.payload,
+    };
+
+    let ix = complete_wrapped(
+        program_id,
+        bridge_id,
+        payer,
+        message_key,
+        post_vaa_data,
+        payload.clone(),
+        to_authority,
+        CompleteWrappedData {},
+    )
+    .unwrap();
+
+    JsValue::from_serde(&ix).unwrap()
+}
+
+#[wasm_bindgen]
+pub fn upgrade_contract_ix(
+    program_id: String,
+    bridge_id: String,
+    payer: String,
+    spill: String,
+    vaa: Vec<u8>,
+) -> JsValue {
+    let program_id = Pubkey::from_str(program_id.as_str()).unwrap();
+    let bridge_id = Pubkey::from_str(bridge_id.as_str()).unwrap();
+    let spill = Pubkey::from_str(spill.as_str()).unwrap();
+    let vaa = VAA::deserialize(vaa.as_slice()).unwrap();
+    let payload = GovernancePayloadUpgrade::deserialize(&mut vaa.payload.as_slice()).unwrap();
+    let message_key = bridge::accounts::PostedVAA::<'_, { AccountState::Uninitialized }>::key(
+        &PostedVAADerivationData {
+            payload_hash: hash_vaa(&vaa.clone().into()).to_vec(),
+        },
+        &bridge_id,
+    );
+    let ix = upgrade_contract(
+        program_id,
+        Pubkey::from_str(payer.as_str()).unwrap(),
+        message_key,
+        Pubkey::new(&vaa.emitter_address),
+        payload.new_contract,
+        spill,
+        vaa.sequence,
+    );
+    return JsValue::from_serde(&ix).unwrap();
+}
+
+#[wasm_bindgen]
+pub fn register_chain_ix(
+    program_id: String,
+    bridge_id: String,
+    payer: String,
+    vaa: Vec<u8>,
+) -> JsValue {
+    let program_id = Pubkey::from_str(program_id.as_str()).unwrap();
+    let bridge_id = Pubkey::from_str(bridge_id.as_str()).unwrap();
+    let payer = Pubkey::from_str(payer.as_str()).unwrap();
+    let vaa = VAA::deserialize(vaa.as_slice()).unwrap();
+    let payload = PayloadGovernanceRegisterChain::deserialize(&mut vaa.payload.as_slice()).unwrap();
+    let message_key = bridge::accounts::PostedVAA::<'_, { AccountState::Uninitialized }>::key(
+        &PostedVAADerivationData {
+            payload_hash: hash_vaa(&vaa.clone().into()).to_vec(),
+        },
+        &bridge_id,
+    );
+    let post_vaa_data = PostVAAData {
+        version: vaa.version,
+        guardian_set_index: vaa.guardian_set_index,
+        timestamp: vaa.timestamp,
+        nonce: vaa.nonce,
+        emitter_chain: vaa.emitter_chain,
+        emitter_address: vaa.emitter_address,
+        sequence: vaa.sequence,
+        consistency_level: vaa.consistency_level,
+        payload: vaa.payload,
+    };
+    let ix = register_chain(
+        program_id,
+        bridge_id,
+        payer,
+        message_key,
+        post_vaa_data,
+        payload,
+        RegisterChainData {},
+    )
+    .unwrap();
+    return JsValue::from_serde(&ix).unwrap();
+}
+
+#[wasm_bindgen]
+pub fn emitter_address(program_id: String) -> Vec<u8> {
+    let program_id = Pubkey::from_str(program_id.as_str()).unwrap();
+    let emitter = EmitterAccount::key(None, &program_id);
+
+    emitter.to_bytes().to_vec()
+}
+
+#[wasm_bindgen]
+pub fn approval_authority_address(program_id: String) -> Vec<u8> {
+    let program_id = Pubkey::from_str(program_id.as_str()).unwrap();
+    let approval_authority = AuthoritySigner::key(None, &program_id);
+
+    approval_authority.to_bytes().to_vec()
+}
+
+#[wasm_bindgen]
+pub fn wrapped_address(
+    program_id: String,
+    token_address: Vec<u8>,
+    token_chain: u16,
+    token_id: Vec<u8>,
+) -> Vec<u8> {
+    let program_id = Pubkey::from_str(program_id.as_str()).unwrap();
+    let mut t_addr = [0u8; 32];
+    t_addr.copy_from_slice(&token_address);
+    let token_id = U256::from_little_endian(token_id.as_slice());
+
+    let wrapped_addr = WrappedMint::<'_, { AccountState::Initialized }>::key(
+        &WrappedDerivationData {
+            token_address: t_addr,
+            token_chain,
+            token_id,
+        },
+        &program_id,
+    );
+
+    wrapped_addr.to_bytes().to_vec()
+}
+
+#[wasm_bindgen]
+pub fn wrapped_meta_address(program_id: String, mint_address: Vec<u8>) -> Vec<u8> {
+    let program_id = Pubkey::from_str(program_id.as_str()).unwrap();
+    let mint_key = Pubkey::new(mint_address.as_slice());
+
+    let wrapped_meta_addr = WrappedTokenMeta::<'_, { AccountState::Initialized }>::key(
+        &WrappedMetaDerivationData { mint_key },
+        &program_id,
+    );
+
+    wrapped_meta_addr.to_bytes().to_vec()
+}
+
+#[wasm_bindgen]
+pub fn parse_wrapped_meta(data: Vec<u8>) -> JsValue {
+    JsValue::from_serde(&WrappedMeta::try_from_slice(data.as_slice()).unwrap()).unwrap()
+}
+
+#[wasm_bindgen]
+pub fn parse_endpoint_registration(data: Vec<u8>) -> JsValue {
+    JsValue::from_serde(&EndpointRegistration::try_from_slice(data.as_slice()).unwrap()).unwrap()
+}

+ 767 - 0
solana/modules/nft_bridge/program/tests/common.rs

@@ -0,0 +1,767 @@
+#![allow(warnings)]
+
+use borsh::{
+    BorshDeserialize,
+    BorshSerialize,
+};
+use byteorder::{
+    BigEndian,
+    WriteBytesExt,
+};
+use hex_literal::hex;
+use secp256k1::{
+    Message as Secp256k1Message,
+    PublicKey,
+    SecretKey,
+};
+use sha3::Digest;
+use solana_client::{
+    client_error::ClientError,
+    rpc_client::RpcClient,
+    rpc_config::RpcSendTransactionConfig,
+};
+use solana_program::{
+    borsh::try_from_slice_unchecked,
+    hash,
+    instruction::{
+        AccountMeta,
+        Instruction,
+    },
+    program_pack::Pack,
+    pubkey::Pubkey,
+    system_instruction::{
+        self,
+        create_account,
+    },
+    system_program,
+    sysvar,
+};
+use solana_sdk::{
+    commitment_config::CommitmentConfig,
+    rent::Rent,
+    secp256k1_instruction::new_secp256k1_instruction,
+    signature::{
+        read_keypair_file,
+        Keypair,
+        Signature,
+        Signer,
+    },
+    transaction::Transaction,
+};
+use spl_token::state::Mint;
+use std::{
+    convert::TryInto,
+    env,
+    io::{
+        Cursor,
+        Write,
+    },
+    time::{
+        Duration,
+        SystemTime,
+    },
+};
+
+use token_bridge::{
+    accounts::*,
+    instruction,
+    instructions,
+    types::*,
+    Initialize,
+};
+
+use solitaire::{
+    processors::seeded::Seeded,
+    AccountState,
+};
+
+pub use helpers::*;
+
+/// Simple API wrapper for quickly preparing and sending transactions.
+pub fn execute(
+    client: &RpcClient,
+    payer: &Keypair,
+    signers: &[&Keypair],
+    instructions: &[Instruction],
+    commitment_level: CommitmentConfig,
+) -> Result<Signature, ClientError> {
+    let mut transaction = Transaction::new_with_payer(instructions, Some(&payer.pubkey()));
+    let recent_blockhash = client.get_recent_blockhash().unwrap().0;
+    transaction.sign(&signers.to_vec(), recent_blockhash);
+    client.send_and_confirm_transaction_with_spinner_and_config(
+        &transaction,
+        commitment_level,
+        RpcSendTransactionConfig {
+            skip_preflight: true,
+            preflight_commitment: None,
+            encoding: None,
+        },
+    )
+}
+
+mod helpers {
+    use bridge::types::{
+        ConsistencyLevel,
+        PostedVAAData,
+    };
+    use token_bridge::{
+        CompleteNativeData,
+        CompleteWrappedData,
+        CreateWrappedData,
+        RegisterChainData,
+        TransferNativeData,
+        TransferWrappedData,
+    };
+
+    use super::*;
+    use bridge::{
+        accounts::{
+            FeeCollector,
+            PostedVAADerivationData,
+        },
+        PostVAAData,
+    };
+    use std::ops::Add;
+    use token_bridge::messages::{
+        PayloadAssetMeta,
+        PayloadGovernanceRegisterChain,
+        PayloadTransfer,
+    };
+
+    /// Initialize the test environment, spins up a solana-test-validator in the background so that
+    /// each test has a fresh environment to work within.
+    pub fn setup() -> (Keypair, RpcClient, Pubkey, Pubkey) {
+        let payer = env::var("BRIDGE_PAYER").unwrap_or("./payer.json".to_string());
+        let rpc_address = env::var("BRIDGE_RPC").unwrap_or("http://127.0.0.1:8899".to_string());
+        let payer = read_keypair_file(payer).unwrap();
+        let rpc = RpcClient::new(rpc_address);
+
+        let (program, token_program) = (
+            env::var("BRIDGE_PROGRAM")
+                .unwrap_or("Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o".to_string())
+                .parse::<Pubkey>()
+                .unwrap(),
+            env::var("TOKEN_BRIDGE_PROGRAM")
+                .unwrap_or("B6RHG3mfcckmrYN1UhmJzyS1XX3fZKbkeUcpJe9Sy3FE".to_string())
+                .parse::<Pubkey>()
+                .unwrap(),
+        );
+
+        (payer, rpc, program, token_program)
+    }
+
+    /// Wait for a single transaction to fully finalize, guaranteeing chain state has been
+    /// confirmed. Useful for consistently fetching data during state checks.
+    pub fn sync(client: &RpcClient, payer: &Keypair) {
+        execute(
+            client,
+            payer,
+            &[payer],
+            &[system_instruction::transfer(
+                &payer.pubkey(),
+                &payer.pubkey(),
+                1,
+            )],
+            CommitmentConfig::finalized(),
+        )
+        .unwrap();
+    }
+
+    /// Fetch account data, the loop is there to re-attempt until data is available.
+    pub fn get_account_data<T: BorshDeserialize>(
+        client: &RpcClient,
+        account: &Pubkey,
+    ) -> Option<T> {
+        let account = client
+            .get_account_with_commitment(account, CommitmentConfig::processed())
+            .unwrap();
+        T::try_from_slice(&account.value.unwrap().data).ok()
+    }
+
+    pub fn initialize_bridge(
+        client: &RpcClient,
+        program: &Pubkey,
+        payer: &Keypair,
+    ) -> Result<Signature, ClientError> {
+        let initial_guardians = &[[1u8; 20]];
+        execute(
+            client,
+            payer,
+            &[payer],
+            &[bridge::instructions::initialize(
+                *program,
+                payer.pubkey(),
+                50,
+                2_000_000_000,
+                initial_guardians,
+            )
+            .unwrap()],
+            CommitmentConfig::processed(),
+        )
+    }
+
+    pub fn transfer(
+        client: &RpcClient,
+        from: &Keypair,
+        to: &Pubkey,
+        lamports: u64,
+    ) -> Result<Signature, ClientError> {
+        execute(
+            client,
+            from,
+            &[from],
+            &[system_instruction::transfer(&from.pubkey(), to, lamports)],
+            CommitmentConfig::processed(),
+        )
+    }
+
+    pub fn initialize(
+        client: &RpcClient,
+        program: &Pubkey,
+        payer: &Keypair,
+        bridge: &Pubkey,
+    ) -> Result<Signature, ClientError> {
+        let instruction = instructions::initialize(*program, payer.pubkey(), *bridge)
+            .expect("Could not create Initialize instruction");
+
+        for account in instruction.accounts.iter().enumerate() {
+            println!("{}: {}", account.0, account.1.pubkey);
+        }
+
+        execute(
+            client,
+            payer,
+            &[payer],
+            &[instruction],
+            CommitmentConfig::processed(),
+        )
+    }
+
+    pub fn attest(
+        client: &RpcClient,
+        program: &Pubkey,
+        bridge: &Pubkey,
+        payer: &Keypair,
+        message: &Keypair,
+        mint: Pubkey,
+        nonce: u32,
+    ) -> Result<Signature, ClientError> {
+        let mint_data = Mint::unpack(
+            &client
+                .get_account_with_commitment(&mint, CommitmentConfig::processed())?
+                .value
+                .unwrap()
+                .data,
+        )
+        .expect("Could not unpack Mint");
+
+        let instruction = instructions::attest(
+            *program,
+            *bridge,
+            payer.pubkey(),
+            message.pubkey(),
+            mint,
+            nonce,
+        )
+        .expect("Could not create Attest instruction");
+
+        for account in instruction.accounts.iter().enumerate() {
+            println!("{}: {}", account.0, account.1.pubkey);
+        }
+
+        execute(
+            client,
+            payer,
+            &[payer, message],
+            &[instruction],
+            CommitmentConfig::processed(),
+        )
+    }
+
+    pub fn transfer_native(
+        client: &RpcClient,
+        program: &Pubkey,
+        bridge: &Pubkey,
+        payer: &Keypair,
+        message: &Keypair,
+        from: &Keypair,
+        from_owner: &Keypair,
+        mint: Pubkey,
+        amount: u64,
+    ) -> Result<Signature, ClientError> {
+        let instruction = instructions::transfer_native(
+            *program,
+            *bridge,
+            payer.pubkey(),
+            message.pubkey(),
+            from.pubkey(),
+            mint,
+            TransferNativeData {
+                nonce: 0,
+                amount,
+                fee: 0,
+                target_address: [0u8; 32],
+                target_chain: 2,
+            },
+        )
+        .expect("Could not create Transfer Native");
+
+        for account in instruction.accounts.iter().enumerate() {
+            println!("{}: {}", account.0, account.1.pubkey);
+        }
+
+        execute(
+            client,
+            payer,
+            &[payer, from_owner, message],
+            &[
+                spl_token::instruction::approve(
+                    &spl_token::id(),
+                    &from.pubkey(),
+                    &token_bridge::accounts::AuthoritySigner::key(None, program),
+                    &from_owner.pubkey(),
+                    &[],
+                    amount,
+                )
+                .unwrap(),
+                instruction,
+            ],
+            CommitmentConfig::processed(),
+        )
+    }
+
+    pub fn transfer_wrapped(
+        client: &RpcClient,
+        program: &Pubkey,
+        bridge: &Pubkey,
+        payer: &Keypair,
+        message: &Keypair,
+        from: Pubkey,
+        from_owner: &Keypair,
+        token_chain: u16,
+        token_address: Address,
+        amount: u64,
+    ) -> Result<Signature, ClientError> {
+        let instruction = instructions::transfer_wrapped(
+            *program,
+            *bridge,
+            payer.pubkey(),
+            message.pubkey(),
+            from,
+            from_owner.pubkey(),
+            token_chain,
+            token_address,
+            TransferWrappedData {
+                nonce: 0,
+                amount,
+                fee: 0,
+                target_address: [5u8; 32],
+                target_chain: 2,
+            },
+        )
+        .expect("Could not create Transfer Native");
+
+        for account in instruction.accounts.iter().enumerate() {
+            println!("{}: {}", account.0, account.1.pubkey);
+        }
+
+        execute(
+            client,
+            payer,
+            &[payer, from_owner, message],
+            &[
+                spl_token::instruction::approve(
+                    &spl_token::id(),
+                    &from,
+                    &token_bridge::accounts::AuthoritySigner::key(None, program),
+                    &from_owner.pubkey(),
+                    &[],
+                    amount,
+                )
+                .unwrap(),
+                instruction,
+            ],
+            CommitmentConfig::processed(),
+        )
+    }
+
+    pub fn register_chain(
+        client: &RpcClient,
+        program: &Pubkey,
+        bridge: &Pubkey,
+        message_acc: &Pubkey,
+        vaa: PostVAAData,
+        payload: PayloadGovernanceRegisterChain,
+        payer: &Keypair,
+    ) -> Result<Signature, ClientError> {
+        let instruction = instructions::register_chain(
+            *program,
+            *bridge,
+            payer.pubkey(),
+            *message_acc,
+            vaa,
+            payload,
+            RegisterChainData {},
+        )
+        .expect("Could not create Transfer Native");
+
+        for account in instruction.accounts.iter().enumerate() {
+            println!("{}: {}", account.0, account.1.pubkey);
+        }
+
+        execute(
+            client,
+            payer,
+            &[payer],
+            &[instruction],
+            CommitmentConfig::processed(),
+        )
+    }
+
+    pub fn complete_native(
+        client: &RpcClient,
+        program: &Pubkey,
+        bridge: &Pubkey,
+        message_acc: &Pubkey,
+        vaa: PostVAAData,
+        payload: PayloadTransfer,
+        payer: &Keypair,
+    ) -> Result<Signature, ClientError> {
+        let instruction = instructions::complete_native(
+            *program,
+            *bridge,
+            payer.pubkey(),
+            *message_acc,
+            vaa,
+            Pubkey::new(&payload.to[..]),
+            None,
+            Pubkey::new(&payload.token_address[..]),
+            CompleteNativeData {},
+        )
+        .expect("Could not create Complete Native instruction");
+
+        for account in instruction.accounts.iter().enumerate() {
+            println!("{}: {}", account.0, account.1.pubkey);
+        }
+
+        execute(
+            client,
+            payer,
+            &[payer],
+            &[instruction],
+            CommitmentConfig::processed(),
+        )
+    }
+
+    pub fn complete_transfer_wrapped(
+        client: &RpcClient,
+        program: &Pubkey,
+        bridge: &Pubkey,
+        message_acc: &Pubkey,
+        vaa: PostVAAData,
+        payload: PayloadTransfer,
+        payer: &Keypair,
+    ) -> Result<Signature, ClientError> {
+        let to = Pubkey::new(&payload.to[..]);
+
+        let instruction = instructions::complete_wrapped(
+            *program,
+            *bridge,
+            payer.pubkey(),
+            *message_acc,
+            vaa,
+            payload,
+            to,
+            None,
+            CompleteWrappedData {},
+        )
+        .expect("Could not create Complete Wrapped instruction");
+
+        for account in instruction.accounts.iter().enumerate() {
+            println!("{}: {}", account.0, account.1.pubkey);
+        }
+
+        execute(
+            client,
+            payer,
+            &[payer],
+            &[instruction],
+            CommitmentConfig::processed(),
+        )
+    }
+
+    pub fn create_wrapped(
+        client: &RpcClient,
+        program: &Pubkey,
+        bridge: &Pubkey,
+        message_acc: &Pubkey,
+        vaa: PostVAAData,
+        payload: PayloadAssetMeta,
+        payer: &Keypair,
+    ) -> Result<Signature, ClientError> {
+        let instruction = instructions::create_wrapped(
+            *program,
+            *bridge,
+            payer.pubkey(),
+            *message_acc,
+            vaa,
+            payload,
+            CreateWrappedData {},
+        )
+        .expect("Could not create Create Wrapped instruction");
+
+        for account in instruction.accounts.iter().enumerate() {
+            println!("{}: {}", account.0, account.1.pubkey);
+        }
+
+        execute(
+            client,
+            payer,
+            &[payer],
+            &[instruction],
+            CommitmentConfig::processed(),
+        )
+    }
+
+    pub fn create_mint(
+        client: &RpcClient,
+        payer: &Keypair,
+        mint_authority: &Pubkey,
+        mint: &Keypair,
+    ) -> Result<Signature, ClientError> {
+        execute(
+            client,
+            payer,
+            &[payer, mint],
+            &[
+                solana_sdk::system_instruction::create_account(
+                    &payer.pubkey(),
+                    &mint.pubkey(),
+                    Rent::default().minimum_balance(spl_token::state::Mint::LEN),
+                    spl_token::state::Mint::LEN as u64,
+                    &spl_token::id(),
+                ),
+                spl_token::instruction::initialize_mint(
+                    &spl_token::id(),
+                    &mint.pubkey(),
+                    mint_authority,
+                    None,
+                    0,
+                )
+                .unwrap(),
+            ],
+            CommitmentConfig::processed(),
+        )
+    }
+
+    pub fn create_spl_metadata(
+        client: &RpcClient,
+        payer: &Keypair,
+        metadata_account: &Pubkey,
+        mint_authority: &Keypair,
+        mint: &Keypair,
+        update_authority: &Pubkey,
+        name: String,
+        symbol: String,
+    ) -> Result<Signature, ClientError> {
+        execute(
+            client,
+            payer,
+            &[payer, mint_authority],
+            &[spl_token_metadata::instruction::create_metadata_accounts(
+                spl_token_metadata::id(),
+                *metadata_account,
+                mint.pubkey(),
+                mint_authority.pubkey(),
+                payer.pubkey(),
+                *update_authority,
+                name,
+                symbol,
+                "https://token.org".to_string(),
+                None,
+                0,
+                false,
+                false,
+            )],
+            CommitmentConfig::processed(),
+        )
+    }
+
+    pub fn create_token_account(
+        client: &RpcClient,
+        payer: &Keypair,
+        token_acc: &Keypair,
+        token_authority: Pubkey,
+        mint: Pubkey,
+    ) -> Result<Signature, ClientError> {
+        execute(
+            client,
+            payer,
+            &[payer, token_acc],
+            &[
+                solana_sdk::system_instruction::create_account(
+                    &payer.pubkey(),
+                    &token_acc.pubkey(),
+                    Rent::default().minimum_balance(spl_token::state::Account::LEN),
+                    spl_token::state::Account::LEN as u64,
+                    &spl_token::id(),
+                ),
+                spl_token::instruction::initialize_account(
+                    &spl_token::id(),
+                    &token_acc.pubkey(),
+                    &mint,
+                    &token_authority,
+                )
+                .unwrap(),
+            ],
+            CommitmentConfig::processed(),
+        )
+    }
+
+    pub fn mint_tokens(
+        client: &RpcClient,
+        payer: &Keypair,
+        mint_authority: &Keypair,
+        mint: &Keypair,
+        token_account: &Pubkey,
+        amount: u64,
+    ) -> Result<Signature, ClientError> {
+        execute(
+            client,
+            payer,
+            &[payer, &mint_authority],
+            &[spl_token::instruction::mint_to(
+                &spl_token::id(),
+                &mint.pubkey(),
+                token_account,
+                &mint_authority.pubkey(),
+                &[],
+                amount,
+            )
+            .unwrap()],
+            CommitmentConfig::processed(),
+        )
+    }
+
+    /// Utility function for generating VAA's from message data.
+    pub fn generate_vaa(
+        emitter: Address,
+        emitter_chain: u16,
+        data: Vec<u8>,
+        nonce: u32,
+        sequence: u64,
+    ) -> (PostVAAData, [u8; 32], [u8; 32]) {
+        let mut vaa = PostVAAData {
+            version: 0,
+            guardian_set_index: 0,
+
+            // Body part
+            emitter_chain,
+            emitter_address: emitter,
+            sequence,
+            payload: data,
+            timestamp: SystemTime::now()
+                .duration_since(SystemTime::UNIX_EPOCH)
+                .unwrap()
+                .as_secs() as u32,
+            nonce,
+            consistency_level: ConsistencyLevel::Confirmed as u8,
+        };
+
+        // Hash data, the thing we wish to actually sign.
+        let body = {
+            let mut v = Cursor::new(Vec::new());
+            v.write_u32::<BigEndian>(vaa.timestamp).unwrap();
+            v.write_u32::<BigEndian>(vaa.nonce).unwrap();
+            v.write_u16::<BigEndian>(vaa.emitter_chain).unwrap();
+            v.write(&vaa.emitter_address).unwrap();
+            v.write_u64::<BigEndian>(vaa.sequence).unwrap();
+            v.write_u8(vaa.consistency_level).unwrap();
+            v.write(&vaa.payload).unwrap();
+            v.into_inner()
+        };
+
+        // Hash this body, which is expected to be the same as the hash currently stored in the
+        // signature account, binding that set of signatures to this VAA.
+        let body: [u8; 32] = {
+            let mut h = sha3::Keccak256::default();
+            h.write(body.as_slice()).unwrap();
+            h.finalize().into()
+        };
+
+        let body_hash: [u8; 32] = {
+            let mut h = sha3::Keccak256::default();
+            h.write(&body).unwrap();
+            h.finalize().into()
+        };
+
+        (vaa, body, body_hash)
+    }
+
+    pub fn post_vaa(
+        client: &RpcClient,
+        program: &Pubkey,
+        payer: &Keypair,
+        vaa: PostVAAData,
+    ) -> Result<(), ClientError> {
+        let instruction =
+            bridge::instructions::post_vaa(*program, payer.pubkey(), Pubkey::new_unique(), vaa);
+
+        for account in instruction.accounts.iter().enumerate() {
+            println!("{}: {}", account.0, account.1.pubkey);
+        }
+
+        execute(
+            client,
+            payer,
+            &[payer],
+            &[instruction],
+            CommitmentConfig::processed(),
+        )?;
+
+        Ok(())
+    }
+
+    pub fn post_message(
+        client: &RpcClient,
+        program: &Pubkey,
+        payer: &Keypair,
+        emitter: &Keypair,
+        message: &Keypair,
+        nonce: u32,
+        data: Vec<u8>,
+        fee: u64,
+    ) -> Result<(), ClientError> {
+        // Transfer money into the fee collector as it needs a balance/must exist.
+        let fee_collector = FeeCollector::<'_>::key(None, program);
+
+        // Capture the resulting message, later functions will need this.
+        let instruction = bridge::instructions::post_message(
+            *program,
+            payer.pubkey(),
+            emitter.pubkey(),
+            message.pubkey(),
+            nonce,
+            data,
+            ConsistencyLevel::Confirmed,
+        )
+        .unwrap();
+
+        for account in instruction.accounts.iter().enumerate() {
+            println!("{}: {}", account.0, account.1.pubkey);
+        }
+
+        execute(
+            client,
+            payer,
+            &[payer, emitter, message],
+            &[
+                system_instruction::transfer(&payer.pubkey(), &fee_collector, fee),
+                instruction,
+            ],
+            CommitmentConfig::processed(),
+        )?;
+
+        Ok(())
+    }
+}

+ 631 - 0
solana/modules/nft_bridge/program/tests/integration.rs

@@ -0,0 +1,631 @@
+#![allow(warnings)]
+
+use borsh::BorshSerialize;
+use byteorder::{
+    BigEndian,
+    WriteBytesExt,
+};
+use hex_literal::hex;
+use rand::Rng;
+use secp256k1::{
+    Message as Secp256k1Message,
+    PublicKey,
+    SecretKey,
+};
+use sha3::Digest;
+use solana_client::rpc_client::RpcClient;
+use solana_program::{
+    borsh::try_from_slice_unchecked,
+    hash,
+    instruction::{
+        AccountMeta,
+        Instruction,
+    },
+    program_pack::Pack,
+    pubkey::Pubkey,
+    system_instruction::{
+        self,
+        create_account,
+    },
+    system_program,
+    sysvar,
+};
+use solana_sdk::{
+    commitment_config::CommitmentConfig,
+    signature::{
+        read_keypair_file,
+        Keypair,
+        Signer,
+    },
+    transaction::Transaction,
+};
+use solitaire::{
+    processors::seeded::Seeded,
+    AccountState,
+};
+use spl_token::state::Mint;
+use std::{
+    convert::TryInto,
+    io::{
+        Cursor,
+        Write,
+    },
+    time::{
+        Duration,
+        SystemTime,
+    },
+};
+
+use bridge::{
+    accounts::{
+        Bridge,
+        FeeCollector,
+        GuardianSet,
+        GuardianSetDerivationData,
+        PostedVAA,
+        PostedVAADerivationData,
+        SignatureSet,
+    },
+    instruction,
+    types::{
+        BridgeConfig,
+        BridgeData,
+        GovernancePayloadGuardianSetChange,
+        GovernancePayloadSetMessageFee,
+        GovernancePayloadTransferFees,
+        GuardianSetData,
+        MessageData,
+        PostedVAAData,
+        SequenceTracker,
+        SignatureSet as SignatureSetData,
+    },
+    Initialize,
+    PostVAA,
+    PostVAAData,
+    SerializePayload,
+    Signature,
+};
+use primitive_types::U256;
+use std::{
+    collections::HashMap,
+    str::FromStr,
+    time::UNIX_EPOCH,
+};
+use token_bridge::{
+    accounts::{
+        EmitterAccount,
+        WrappedDerivationData,
+        WrappedMint,
+    },
+    messages::{
+        PayloadAssetMeta,
+        PayloadGovernanceRegisterChain,
+        PayloadTransfer,
+    },
+    types::Address,
+};
+
+mod common;
+
+const GOVERNANCE_KEY: [u8; 64] = [
+    240, 133, 120, 113, 30, 67, 38, 184, 197, 72, 234, 99, 241, 21, 58, 225, 41, 157, 171, 44, 196,
+    163, 134, 236, 92, 148, 110, 68, 127, 114, 177, 0, 173, 253, 199, 9, 242, 142, 201, 174, 108,
+    197, 18, 102, 115, 0, 31, 205, 127, 188, 191, 56, 171, 228, 20, 247, 149, 170, 141, 231, 147,
+    88, 97, 199,
+];
+
+struct Context {
+    /// Address of the core bridge contract.
+    bridge: Pubkey,
+
+    /// Shared RPC client for tests to make transactions with.
+    client: RpcClient,
+
+    /// Payer key with a ton of lamports to ease testing with.
+    payer: Keypair,
+
+    /// Track nonces throughout the tests.
+    seq: Sequencer,
+
+    /// Address of the token bridge itself that we wish to test.
+    token_bridge: Pubkey,
+
+    /// Keypairs for mint information, required in multiple tests.
+    mint_authority: Keypair,
+    mint: Keypair,
+    mint_meta: Pubkey,
+
+    /// Keypairs for test token accounts.
+    token_authority: Keypair,
+    token_account: Keypair,
+    metadata_account: Pubkey,
+}
+
+/// Small helper to track and provide sequences during tests. This is in particular needed for
+/// guardian operations that require them for derivations.
+struct Sequencer {
+    sequences: HashMap<[u8; 32], u64>,
+}
+
+impl Sequencer {
+    fn next(&mut self, emitter: [u8; 32]) -> u64 {
+        let entry = self.sequences.entry(emitter).or_insert(0);
+        *entry += 1;
+        *entry - 1
+    }
+
+    fn peek(&mut self, emitter: [u8; 32]) -> u64 {
+        *self.sequences.entry(emitter).or_insert(0)
+    }
+}
+
+#[test]
+fn run_integration_tests() {
+    let (payer, client, bridge, token_bridge) = common::setup();
+
+    // Setup a Bridge to test against.
+    println!("Bridge: {}", bridge);
+    common::initialize_bridge(&client, &bridge, &payer);
+
+    // Context for test environment.
+    let mint = Keypair::new();
+    let mint_pubkey = mint.pubkey();
+    let metadata_pubkey = Pubkey::from_str("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s").unwrap();
+
+    // SPL Token Meta
+    let metadata_seeds = &[
+        "metadata".as_bytes(),
+        metadata_pubkey.as_ref(),
+        mint_pubkey.as_ref(),
+    ];
+
+    let (metadata_key, metadata_bump_seed) = Pubkey::find_program_address(
+        metadata_seeds,
+        &Pubkey::from_str("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s").unwrap(),
+    );
+
+    // Token Bridge Meta
+    use token_bridge::accounts::WrappedTokenMeta;
+    let metadata_account = WrappedTokenMeta::<'_, { AccountState::Uninitialized }>::key(
+        &token_bridge::accounts::WrappedMetaDerivationData {
+            mint_key: mint_pubkey.clone(),
+        },
+        &token_bridge,
+    );
+
+    let mut context = Context {
+        seq: Sequencer {
+            sequences: HashMap::new(),
+        },
+        bridge,
+        client,
+        payer,
+        token_bridge,
+        mint_authority: Keypair::new(),
+        mint,
+        mint_meta: metadata_account,
+        token_account: Keypair::new(),
+        token_authority: Keypair::new(),
+        metadata_account: metadata_key,
+    };
+
+    // Create a mint for use within tests.
+    common::create_mint(
+        &context.client,
+        &context.payer,
+        &context.mint_authority.pubkey(),
+        &context.mint,
+    )
+    .unwrap();
+
+    // Create Token accounts for use within tests.
+    common::create_token_account(
+        &context.client,
+        &context.payer,
+        &context.token_account,
+        context.token_authority.pubkey(),
+        context.mint.pubkey(),
+    )
+    .unwrap();
+
+    // Mint tokens
+    common::mint_tokens(
+        &context.client,
+        &context.payer,
+        &context.mint_authority,
+        &context.mint,
+        &context.token_account.pubkey(),
+        1000,
+    )
+    .unwrap();
+
+    // Initialize the bridge and verify the bridges state.
+    test_initialize(&mut context);
+    test_transfer_native(&mut context);
+    test_attest(&mut context);
+    test_register_chain(&mut context);
+    test_transfer_native_in(&mut context);
+
+    // Create an SPL Metadata account to test attestations for wrapped tokens.
+    common::create_spl_metadata(
+        &context.client,
+        &context.payer,
+        &context.metadata_account,
+        &context.mint_authority,
+        &context.mint,
+        &context.payer.pubkey(),
+        "BTC".to_string(),
+        "Bitcoin".to_string(),
+    )
+    .unwrap();
+
+    let wrapped = test_create_wrapped(&mut context);
+    let wrapped_acc = Keypair::new();
+    common::create_token_account(
+        &context.client,
+        &context.payer,
+        &wrapped_acc,
+        context.token_authority.pubkey(),
+        wrapped,
+    )
+    .unwrap();
+    test_transfer_wrapped_in(&mut context, wrapped_acc.pubkey());
+    test_transfer_wrapped(&mut context, wrapped_acc.pubkey());
+}
+
+fn test_attest(context: &mut Context) -> () {
+    println!("Attest");
+    use token_bridge::{
+        accounts::ConfigAccount,
+        types::Config,
+    };
+
+    let Context {
+        ref payer,
+        ref client,
+        ref bridge,
+        ref token_bridge,
+        ref mint_authority,
+        ref mint,
+        ref mint_meta,
+        ref metadata_account,
+        ..
+    } = context;
+
+    let message = &Keypair::new();
+
+    common::attest(
+        client,
+        token_bridge,
+        bridge,
+        payer,
+        message,
+        mint.pubkey(),
+        0,
+    )
+    .unwrap();
+
+    let emitter_key = EmitterAccount::key(None, &token_bridge);
+    let mint_data = Mint::unpack(
+        &client
+            .get_account_with_commitment(&mint.pubkey(), CommitmentConfig::processed())
+            .unwrap()
+            .value
+            .unwrap()
+            .data,
+    )
+    .unwrap();
+    let payload = PayloadAssetMeta {
+        token_address: mint.pubkey().to_bytes(),
+        token_chain: 1,
+        decimals: mint_data.decimals,
+        symbol: "USD".to_string(),
+        name: "Bitcoin".to_string(),
+    };
+    let payload = payload.try_to_vec().unwrap();
+}
+
+fn test_transfer_native(context: &mut Context) -> () {
+    println!("Transfer Native");
+    use token_bridge::{
+        accounts::ConfigAccount,
+        types::Config,
+    };
+
+    let Context {
+        ref payer,
+        ref client,
+        ref bridge,
+        ref token_bridge,
+        ref mint_authority,
+        ref mint,
+        ref mint_meta,
+        ref token_account,
+        ref token_authority,
+        ..
+    } = context;
+
+    let message = &Keypair::new();
+
+    common::transfer_native(
+        client,
+        token_bridge,
+        bridge,
+        payer,
+        message,
+        token_account,
+        token_authority,
+        mint.pubkey(),
+        100,
+    )
+    .unwrap();
+}
+
+fn test_transfer_wrapped(context: &mut Context, token_account: Pubkey) -> () {
+    println!("TransferWrapped");
+    use token_bridge::{
+        accounts::ConfigAccount,
+        types::Config,
+    };
+
+    let Context {
+        ref payer,
+        ref client,
+        ref bridge,
+        ref token_bridge,
+        ref mint_authority,
+        ref token_authority,
+        ..
+    } = context;
+
+    let message = &Keypair::new();
+
+    common::transfer_wrapped(
+        client,
+        token_bridge,
+        bridge,
+        payer,
+        message,
+        token_account,
+        token_authority,
+        2,
+        [1u8; 32],
+        10000000,
+    )
+    .unwrap();
+}
+
+fn test_register_chain(context: &mut Context) -> () {
+    println!("Register Chain");
+    use token_bridge::{
+        accounts::ConfigAccount,
+        types::Config,
+    };
+
+    let Context {
+        ref payer,
+        ref client,
+        ref bridge,
+        ref token_bridge,
+        ref mint_authority,
+        ref mint,
+        ref mint_meta,
+        ref token_account,
+        ref token_authority,
+        ..
+    } = context;
+
+    let nonce = rand::thread_rng().gen();
+    let emitter = Keypair::from_bytes(&GOVERNANCE_KEY).unwrap();
+    let payload = PayloadGovernanceRegisterChain {
+        chain: 2,
+        endpoint_address: [0u8; 32],
+    };
+    let message = payload.try_to_vec().unwrap();
+
+    let (vaa, _, _) = common::generate_vaa(emitter.pubkey().to_bytes(), 1, message, nonce, 0);
+    common::post_vaa(client, bridge, payer, vaa.clone()).unwrap();
+
+    let mut msg_derivation_data = &PostedVAADerivationData {
+        payload_hash: bridge::instructions::hash_vaa(&vaa).to_vec(),
+    };
+    let message_key =
+        PostedVAA::<'_, { AccountState::MaybeInitialized }>::key(&msg_derivation_data, &bridge);
+
+    common::register_chain(
+        client,
+        token_bridge,
+        bridge,
+        &message_key,
+        vaa,
+        payload,
+        payer,
+    )
+    .unwrap();
+}
+
+fn test_transfer_native_in(context: &mut Context) -> () {
+    println!("TransferNativeIn");
+    use token_bridge::{
+        accounts::ConfigAccount,
+        types::Config,
+    };
+
+    let Context {
+        ref payer,
+        ref client,
+        ref bridge,
+        ref token_bridge,
+        ref mint_authority,
+        ref mint,
+        ref mint_meta,
+        ref token_account,
+        ref token_authority,
+        ..
+    } = context;
+
+    let nonce = rand::thread_rng().gen();
+
+    let payload = PayloadTransfer {
+        amount: U256::from(100),
+        token_address: mint.pubkey().to_bytes(),
+        token_chain: 1,
+        to: token_account.pubkey().to_bytes(),
+        to_chain: 1,
+        fee: U256::from(0),
+    };
+    let message = payload.try_to_vec().unwrap();
+
+    let (vaa, _, _) = common::generate_vaa([0u8; 32], 2, message, nonce, 1);
+    common::post_vaa(client, bridge, payer, vaa.clone()).unwrap();
+    let mut msg_derivation_data = &PostedVAADerivationData {
+        payload_hash: bridge::instructions::hash_vaa(&vaa).to_vec(),
+    };
+    let message_key =
+        PostedVAA::<'_, { AccountState::MaybeInitialized }>::key(&msg_derivation_data, &bridge);
+
+    common::complete_native(
+        client,
+        token_bridge,
+        bridge,
+        &message_key,
+        vaa,
+        payload,
+        payer,
+    )
+    .unwrap();
+}
+
+fn test_transfer_wrapped_in(context: &mut Context, to: Pubkey) -> () {
+    println!("TransferWrappedIn");
+    use token_bridge::{
+        accounts::ConfigAccount,
+        types::Config,
+    };
+
+    let Context {
+        ref payer,
+        ref client,
+        ref bridge,
+        ref token_bridge,
+        ref mint_authority,
+        ref mint,
+        ref mint_meta,
+        ref token_account,
+        ref token_authority,
+        ..
+    } = context;
+
+    let nonce = rand::thread_rng().gen();
+
+    let payload = PayloadTransfer {
+        amount: U256::from(100000000),
+        token_address: [1u8; 32],
+        token_chain: 2,
+        to: to.to_bytes(),
+        to_chain: 1,
+        fee: U256::from(0),
+    };
+    let message = payload.try_to_vec().unwrap();
+
+    let (vaa, _, _) = common::generate_vaa([0u8; 32], 2, message, nonce, rand::thread_rng().gen());
+    common::post_vaa(client, bridge, payer, vaa.clone()).unwrap();
+    let mut msg_derivation_data = &PostedVAADerivationData {
+        payload_hash: bridge::instructions::hash_vaa(&vaa).to_vec(),
+    };
+    let message_key =
+        PostedVAA::<'_, { AccountState::MaybeInitialized }>::key(&msg_derivation_data, &bridge);
+
+    common::complete_transfer_wrapped(
+        client,
+        token_bridge,
+        bridge,
+        &message_key,
+        vaa,
+        payload,
+        payer,
+    )
+    .unwrap();
+}
+
+fn test_create_wrapped(context: &mut Context) -> (Pubkey) {
+    println!("CreateWrapped");
+    use token_bridge::{
+        accounts::ConfigAccount,
+        types::Config,
+    };
+
+    let Context {
+        ref payer,
+        ref client,
+        ref bridge,
+        ref token_bridge,
+        ref mint_authority,
+        ref mint,
+        ref mint_meta,
+        ref token_account,
+        ref token_authority,
+        ..
+    } = context;
+
+    let nonce = rand::thread_rng().gen();
+
+    let payload = PayloadAssetMeta {
+        token_address: [1u8; 32],
+        token_chain: 2,
+        decimals: 7,
+        symbol: "".to_string(),
+        name: "".to_string(),
+    };
+    let message = payload.try_to_vec().unwrap();
+
+    let (vaa, _, _) = common::generate_vaa([0u8; 32], 2, message, nonce, 2);
+    common::post_vaa(client, bridge, payer, vaa.clone()).unwrap();
+    let mut msg_derivation_data = &PostedVAADerivationData {
+        payload_hash: bridge::instructions::hash_vaa(&vaa).to_vec(),
+    };
+    let message_key =
+        PostedVAA::<'_, { AccountState::MaybeInitialized }>::key(&msg_derivation_data, &bridge);
+
+    common::create_wrapped(
+        client,
+        token_bridge,
+        bridge,
+        &message_key,
+        vaa,
+        payload,
+        payer,
+    )
+    .unwrap();
+
+    return WrappedMint::<'_, { AccountState::Initialized }>::key(
+        &WrappedDerivationData {
+            token_chain: 2,
+            token_address: [1u8; 32],
+        },
+        token_bridge,
+    );
+}
+
+fn test_initialize(context: &mut Context) {
+    println!("Initialize");
+    use token_bridge::{
+        accounts::ConfigAccount,
+        types::Config,
+    };
+
+    let Context {
+        ref payer,
+        ref client,
+        ref bridge,
+        ref token_bridge,
+        ..
+    } = context;
+
+    common::initialize(client, token_bridge, payer, &bridge).unwrap();
+
+    // Verify Token Bridge State
+    let config_key = ConfigAccount::<'_, { AccountState::Uninitialized }>::key(None, &token_bridge);
+    let config: Config = common::get_account_data(client, &config_key).unwrap();
+    assert_eq!(config.wormhole_bridge, *bridge);
+}

+ 13 - 2
solana/modules/token_bridge/client/src/main.rs

@@ -94,6 +94,7 @@ fn command_create_meta(
     mint: &Pubkey,
     name: String,
     symbol: String,
+    uri: String,
 ) -> CommmandResult {
     println!("Creating meta for mint {}", mint);
 
@@ -116,7 +117,7 @@ fn command_create_meta(
         config.owner.pubkey(),
         name,
         symbol,
-        String::from(""),
+        uri,
         None,
         0,
         false,
@@ -247,6 +248,15 @@ fn main() {
                         .index(3)
                         .required(true)
                         .help("Symbol of the token"),
+                )
+                .arg(
+                    Arg::with_name("uri")
+                        .long("uri")
+                        .value_name("URI")
+                        .takes_value(true)
+                        .index(4)
+                        .required(true)
+                        .help("URI of the token metadata"),
                 ),
         )
         .get_matches();
@@ -289,8 +299,9 @@ fn main() {
             let mint = pubkey_of(arg_matches, "mint").unwrap();
             let name: String = value_of(arg_matches, "name").unwrap();
             let symbol: String = value_of(arg_matches, "symbol").unwrap();
+            let uri: String = value_of(arg_matches, "uri").unwrap();
 
-            command_create_meta(&config, &mint, name, symbol)
+            command_create_meta(&config, &mint, name, symbol, uri)
         }
         ("emitter", Some(arg_matches)) => {
             let bridge = pubkey_of(arg_matches, "bridge").unwrap();