Repository: CosmWasm/sylvia
Pull request #1 (2022-05-30 13:18:12)

Moved basic implementation from cosmwasm-plus


Patch:
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..39481ae
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+target/**
+*/target/**
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..5d9457a
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,679 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "anyhow"
+version = "1.0.57"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc"
+
+[[package]]
+name = "base16ct"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce"
+
+[[package]]
+name = "base64"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
+
+[[package]]
+name = "base64ct"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dea908e7347a8c64e378c17e30ef880ad73e3b4498346b055c2c00ea342f3179"
+
+[[package]]
+name = "block-buffer"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "byteorder"
+version = "1.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "const-oid"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3"
+
+[[package]]
+name = "convert_case"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb4a24b1aaf0fd0ce8b45161144d6f42cd91677fd5940fd431183eb023b3a2b8"
+
+[[package]]
+name = "cosmwasm-crypto"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5eb0afef2325df81aadbf9be1233f522ed8f6e91df870c764bc44cca2b1415bd"
+dependencies = [
+ "digest",
+ "ed25519-zebra",
+ "k256",
+ "rand_core 0.6.3",
+ "thiserror",
+]
+
+[[package]]
+name = "cosmwasm-derive"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b36e527620a2a3e00e46b6e731ab6c9b68d11069c986f7d7be8eba79ef081a4"
+dependencies = [
+ "syn",
+]
+
+[[package]]
+name = "cosmwasm-std"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "875994993c2082a6fcd406937bf0fca21c349e4a624f3810253a14fa83a3a195"
+dependencies = [
+ "base64",
+ "cosmwasm-crypto",
+ "cosmwasm-derive",
+ "forward_ref",
+ "schemars",
+ "serde",
+ "serde-json-wasm",
+ "thiserror",
+ "uint",
+]
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crunchy"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
+
+[[package]]
+name = "crypto-bigint"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21"
+dependencies = [
+ "generic-array",
+ "rand_core 0.6.3",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "crypto-mac"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714"
+dependencies = [
+ "generic-array",
+ "subtle",
+]
+
+[[package]]
+name = "curve25519-dalek"
+version = "3.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61"
+dependencies = [
+ "byteorder",
+ "digest",
+ "rand_core 0.5.1",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "cw-storage-plus"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9336ecef1e19d56cf6e3e932475fc6a3dee35eec5a386e07917a1d1ba6bb0e35"
+dependencies = [
+ "cosmwasm-std",
+ "schemars",
+ "serde",
+]
+
+[[package]]
+name = "der"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c"
+dependencies = [
+ "const-oid",
+]
+
+[[package]]
+name = "digest"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "dyn-clone"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21e50f3adc76d6a43f5ed73b698a87d0760ca74617f60f7c3b879003536fdd28"
+
+[[package]]
+name = "ecdsa"
+version = "0.13.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0d69ae62e0ce582d56380743515fefaf1a8c70cec685d9677636d7e30ae9dc9"
+dependencies = [
+ "der",
+ "elliptic-curve",
+ "rfc6979",
+ "signature",
+]
+
+[[package]]
+name = "ed25519-zebra"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "403ef3e961ab98f0ba902771d29f842058578bb1ce7e3c59dad5a6a93e784c69"
+dependencies = [
+ "curve25519-dalek",
+ "hex",
+ "rand_core 0.6.3",
+ "serde",
+ "sha2",
+ "thiserror",
+ "zeroize",
+]
+
+[[package]]
+name = "elliptic-curve"
+version = "0.11.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25b477563c2bfed38a3b7a60964c49e058b2510ad3f12ba3483fd8f62c2306d6"
+dependencies = [
+ "base16ct",
+ "crypto-bigint",
+ "der",
+ "ff",
+ "generic-array",
+ "group",
+ "rand_core 0.6.3",
+ "sec1",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "ff"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924"
+dependencies = [
+ "rand_core 0.6.3",
+ "subtle",
+]
+
+[[package]]
+name = "forward_ref"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e"
+
+[[package]]
+name = "generic-array"
+version = "0.14.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi 0.9.0+wasi-snapshot-preview1",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi 0.10.2+wasi-snapshot-preview1",
+]
+
+[[package]]
+name = "group"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89"
+dependencies = [
+ "ff",
+ "rand_core 0.6.3",
+ "subtle",
+]
+
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
+[[package]]
+name = "hmac"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b"
+dependencies = [
+ "crypto-mac",
+ "digest",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d"
+
+[[package]]
+name = "k256"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19c3a5e0a0b8450278feda242592512e09f61c72e018b8cd5c859482802daf2d"
+dependencies = [
+ "cfg-if",
+ "ecdsa",
+ "elliptic-curve",
+ "sec1",
+ "sha2",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.126"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
+
+[[package]]
+name = "opaque-debug"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
+
+[[package]]
+name = "pkcs8"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7cabda3fb821068a9a4fab19a683eac3af12edf0f34b94a8be53c4972b8149d0"
+dependencies = [
+ "der",
+ "spki",
+ "zeroize",
+]
+
+[[package]]
+name = "proc-macro-error"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
+dependencies = [
+ "proc-macro-error-attr",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro-error-attr"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.39"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1"
+dependencies = [
+ "proc-macro2",
+]
+
+[[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.6",
+]
+
+[[package]]
+name = "rfc6979"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96ef608575f6392792f9ecf7890c00086591d29a83910939d430753f7c050525"
+dependencies = [
+ "crypto-bigint",
+ "hmac",
+ "zeroize",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695"
+
+[[package]]
+name = "schemars"
+version = "0.8.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1847b767a3d62d95cbf3d8a9f0e421cf57a0d8aa4f411d4b16525afb0284d4ed"
+dependencies = [
+ "dyn-clone",
+ "schemars_derive",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "schemars_derive"
+version = "0.8.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af4d7e1b012cb3d9129567661a63755ea4b8a7386d339dc945ae187e403c6743"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "serde_derive_internals",
+ "syn",
+]
+
+[[package]]
+name = "sec1"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1"
+dependencies = [
+ "der",
+ "generic-array",
+ "pkcs8",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.137"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde-cw-value"
+version = "0.7.0"
+source = "git+https://github.com/CosmWasm/serde-cw-value.git#9a141c181a7b661556857dea0f736dd3272c775d"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "serde-cw-value"
+version = "0.7.0"
+source = "git+https://github.com/hashedone/serde-cw-value.git#9a141c181a7b661556857dea0f736dd3272c775d"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "serde-json-wasm"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "479b4dbc401ca13ee8ce902851b834893251404c4f3c65370a49e047a6be09a5"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.137"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_derive_internals"
+version = "0.26.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.81"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "sha2"
+version = "0.9.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800"
+dependencies = [
+ "block-buffer",
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+ "opaque-debug",
+]
+
+[[package]]
+name = "signature"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788"
+dependencies = [
+ "digest",
+ "rand_core 0.6.3",
+]
+
+[[package]]
+name = "spki"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44d01ac02a6ccf3e07db148d2be087da624fea0221a16152ed01f0496a6b0a27"
+dependencies = [
+ "base64ct",
+ "der",
+]
+
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
+name = "subtle"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
+
+[[package]]
+name = "sylvia"
+version = "0.1.1"
+dependencies = [
+ "cosmwasm-std",
+ "schemars",
+ "serde",
+ "serde-cw-value 0.7.0 (git+https://github.com/hashedone/serde-cw-value.git)",
+ "sylvia-derive",
+]
+
+[[package]]
+name = "sylvia-derive"
+version = "0.1.1"
+dependencies = [
+ "anyhow",
+ "convert_case",
+ "cosmwasm-std",
+ "cw-storage-plus",
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "schemars",
+ "serde",
+ "serde-cw-value 0.7.0 (git+https://github.com/CosmWasm/serde-cw-value.git)",
+ "syn",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.95"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "typenum"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
+
+[[package]]
+name = "uint"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0"
+dependencies = [
+ "byteorder",
+ "crunchy",
+ "hex",
+ "static_assertions",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee"
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[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 = "zeroize"
+version = "1.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94693807d016b2f2d2e14420eb3bfcca689311ff775dcf113d74ea624b7cdf07"
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..598aa00
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,2 @@
+[workspace]
+members = ["sylvia", "sylvia-derive"]
diff --git a/LICENSE b/LICENSE
index f4604c5..6b0b127 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,21 +1,203 @@
-MIT License
-
-Copyright (c) 2022 CosmWasm
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..e0af242
--- /dev/null
+++ b/README.md
@@ -0,0 +1,40 @@
+# Sylvia Framework
+
+Sylvia is the old name meaning Spirit of The Wood.
+
+Sylvia is Roman godness of the forrest.
+
+Sylvia is also a framework created to give you the abstraction
+focused and scallable solution for building your CosmWasm Smart Contracts.
+Find your way into the forrest of Cosmos ecosystem. We provide you toolset,
+so insteadd of focusing on raw structure of your contract, you can just
+create them in proper and idiomatic Rust, and then just let cargo make sure,
+that there are sound.
+
+## The approach
+
+CosmWasm ecosystem core provides the base building blocks for builing SmartContracts -
+the `cosmwasm-std` for basic CW bindings, the `cw-storage-plus` for easier state
+manangement, and the `cw-multitest`for testing them. Sylvia framework is build on top
+of them, so creating contracts you don't need to think about messages structure,
+how their API is (de)serialized, or how to handle messages dispatching.
+
+Instead of that, the API of your contract is set of traits, you implement on you
+SC type. Things like entry point structures, functions dispatching the messages, or
+even helpers for multitest are generated by the framework. It allows for better control
+of interfaces, including validating their completeness in compile time. Also as a side
+effect, as Sylvia has all the knowledge about the contract API structure, it can
+generate whole bunch of helpers - utilities for multitests, or even queriers.
+
+## The state
+
+Right now Sylvia is ready to implement simple smart contracts. `Insantiation`, `Execution`, and
+`Query` messages can be implemented easily. `migrate`, `sudo` and `reply` messages are missing
+right now, but all of those can be covered by simply implementing their entry points and
+dispatching manually, so for basic functionality Sylvia also can be used. Sylvia already
+supports oveerriding the chain custom messages, so it is possible to use it with custom blokchains -
+except for handling custom queries and sudo messages. All messages can be generic, so it is even
+possible to create contract eligible to work across several different CosmWasm blockchains.
+
+For now only messages dispatching is generated - queriers utilties and MT helpers are on the
+TODO list, so expect them soon.
diff --git a/sylvia-derive/Cargo.toml b/sylvia-derive/Cargo.toml
new file mode 100644
index 0000000..6c51a26
--- /dev/null
+++ b/sylvia-derive/Cargo.toml
@@ -0,0 +1,29 @@
+[package]
+name = "sylvia-derive"
+version = "0.1.1"
+authors = ["Bartłomiej Kuras <bart.k@confio.gmbh>"]
+edition = "2021"
+description = "Framework for generating trait based CosmWasm smart contracts - derives"
+license = "Apache-2.0"
+repository = "https://github.com/CosmWasm/sylvia"
+homepage = "https://cosmwasm.com"
+keywords = ["cosmwasm", "blockchain", "confio"]
+categories = ["api-bindings", "wasm"]
+
+[lib]
+proc-macro = true
+
+[dependencies]
+syn = { version = "1", features = ["parsing", "derive", "fold", "visit"] }
+quote = "1"
+proc-macro2 = "1"
+convert_case = "0.5"
+proc-macro-error = "1"
+
+[dev-dependencies]
+serde-cw-value = { version = "0.7.0", git = "https://github.com/CosmWasm/serde-cw-value.git" }
+serde = { version = "1", features = ["derive"] }
+cosmwasm-std = "1"
+cw-storage-plus = "0.13.2"
+schemars = "0.8"
+anyhow = "1"
diff --git a/sylvia-derive/NOTICE b/sylvia-derive/NOTICE
new file mode 100644
index 0000000..02583e9
--- /dev/null
+++ b/sylvia-derive/NOTICE
@@ -0,0 +1,14 @@
+cw-derive: Helper for building trait based contracts
+Copyright (C) 2020 Confio OÜ
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/sylvia-derive/examples/basic.rs b/sylvia-derive/examples/basic.rs
new file mode 100644
index 0000000..d3408b3
--- /dev/null
+++ b/sylvia-derive/examples/basic.rs
@@ -0,0 +1,102 @@
+#![allow(dead_code)]
+use anyhow::Error;
+use cosmwasm_std::{Addr, Deps, DepsMut, Env, MessageInfo, Response, StdError};
+use cw_storage_plus::{Item, Map};
+use sylvia_derive::{contract, interface};
+
+#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, schemars::JsonSchema)]
+pub struct Member {
+    addr: String,
+    weight: u64,
+}
+
+#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, schemars::JsonSchema)]
+pub struct MemberResp {
+    weight: u64,
+}
+
+#[interface(module=group)]
+pub trait Group {
+    type Error: From<StdError>;
+
+    #[msg(exec)]
+    fn update_admin(
+        &self,
+        ctx: (DepsMut, Env, MessageInfo),
+        admin: Option<String>,
+    ) -> Result<Response, Self::Error>;
+
+    #[msg(exec)]
+    fn update_members(
+        &self,
+        ctx: (DepsMut, Env, MessageInfo),
+        remove: Vec<String>,
+        add: Vec<Member>,
+    ) -> Result<Response, Self::Error>;
+
+    #[msg(query)]
+    fn member(&self, ctx: (Deps, Env), addr: String) -> Result<MemberResp, Self::Error>;
+}
+
+pub struct GroupContract {
+    admin: Item<'static, Addr>,
+    members: Map<'static, Addr, u64>,
+}
+
+impl Default for GroupContract {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl Group for GroupContract {
+    type Error = Error;
+
+    fn update_admin(
+        &self,
+        _ctx: (DepsMut, Env, MessageInfo),
+        _admin: Option<String>,
+    ) -> Result<Response, Self::Error> {
+        todo!()
+    }
+
+    fn update_members(
+        &self,
+        _ctx: (DepsMut, Env, MessageInfo),
+        _remove: Vec<String>,
+        _add: Vec<Member>,
+    ) -> Result<Response, Self::Error> {
+        todo!()
+    }
+
+    fn member(&self, _ctx: (Deps, Env), _addr: String) -> Result<MemberResp, Self::Error> {
+        todo!()
+    }
+}
+
+#[contract(module=contract, error=Error)]
+#[messages(group as Group)]
+impl GroupContract {
+    pub fn new() -> Self {
+        Self {
+            admin: Item::new("admin"),
+            members: Map::new("members"),
+        }
+    }
+
+    #[msg(instantiate)]
+    pub fn instantiate(
+        &self,
+        (deps, _env, _msg): (DepsMut, Env, MessageInfo),
+        admin: Option<String>,
+    ) -> Result<Response, Error> {
+        if let Some(admin) = admin {
+            let admin = deps.api.addr_validate(&admin)?;
+            self.admin.save(deps.storage, &admin)?;
+        }
+
+        Ok(Response::new())
+    }
+}
+
+fn main() {}
diff --git a/sylvia-derive/examples/generics.rs b/sylvia-derive/examples/generics.rs
new file mode 100644
index 0000000..299d86b
--- /dev/null
+++ b/sylvia-derive/examples/generics.rs
@@ -0,0 +1,21 @@
+#![allow(dead_code)]
+use cosmwasm_std::{CosmosMsg, DepsMut, Env, MessageInfo, Response};
+
+use sylvia_derive::interface;
+
+#[interface(module=msg)]
+pub trait Cw1<Msg>
+where
+    Msg: std::fmt::Debug + PartialEq + Clone + schemars::JsonSchema,
+{
+    type Error;
+
+    #[msg(exec)]
+    fn execute(
+        &self,
+        ctx: (DepsMut, Env, MessageInfo),
+        msgs: Vec<CosmosMsg<Msg>>,
+    ) -> Result<Response, Self::Error>;
+}
+
+fn main() {}
diff --git a/sylvia-derive/src/check_generics.rs b/sylvia-derive/src/check_generics.rs
new file mode 100644
index 0000000..1ce95b0
--- /dev/null
+++ b/sylvia-derive/src/check_generics.rs
@@ -0,0 +1,64 @@
+use syn::visit::Visit;
+use syn::GenericParam;
+
+pub struct CheckGenerics<'g> {
+    generics: &'g [&'g GenericParam],
+    used: Vec<&'g GenericParam>,
+}
+
+impl<'g> CheckGenerics<'g> {
+    pub fn new(generics: &'g [&'g GenericParam]) -> Self {
+        Self {
+            generics,
+            used: vec![],
+        }
+    }
+
+    pub fn used(self) -> Vec<&'g GenericParam> {
+        self.used
+    }
+
+    /// Returns split between used and unused generics
+    pub fn used_unused(self) -> (Vec<&'g GenericParam>, Vec<&'g GenericParam>) {
+        let unused = self
+            .generics
+            .iter()
+            .filter(|gen| !self.used.contains(*gen))
+            .copied()
+            .collect();
+
+        (self.used, unused)
+    }
+}
+
+impl<'ast, 'g> Visit<'ast> for CheckGenerics<'g> {
+    fn visit_lifetime(&mut self, i: &'ast syn::Lifetime) {
+        if let Some(gen) = self
+            .generics
+            .iter()
+            .find(|gen| matches!(gen, GenericParam::Lifetime(lt) if lt.lifetime == *i))
+        {
+            if !self.used.contains(gen) {
+                self.used.push(gen);
+            }
+        }
+    }
+    fn visit_path(&mut self, p: &'ast syn::Path) {
+        if let Some(p) = p.get_ident() {
+            if let Some(gen) = self
+                .generics
+                .iter()
+                .find(|gen| matches!(gen, GenericParam::Type(ty) if ty.ident == *p))
+            {
+                if !self.used.contains(gen) {
+                    self.used.push(gen);
+                }
+            }
+        }
+
+        // Default visit implementation - visiting path deeper
+        for el in &p.segments {
+            self.visit_path_segment(el);
+        }
+    }
+}
diff --git a/sylvia-derive/src/input.rs b/sylvia-derive/src/input.rs
new file mode 100644
index 0000000..83bac3d
--- /dev/null
+++ b/sylvia-derive/src/input.rs
@@ -0,0 +1,134 @@
+use proc_macro2::{Span, TokenStream};
+use proc_macro_error::emit_error;
+use quote::quote;
+use syn::{GenericParam, Ident, ItemImpl, ItemTrait, TraitItem};
+
+use crate::message::{EnumMessage, GlueMessage, StructMessage};
+use crate::parser::{ContractArgs, InterfaceArgs, MsgType};
+
+/// Preprocessed `interface` macro input
+pub struct TraitInput<'a> {
+    attributes: &'a InterfaceArgs,
+    item: &'a ItemTrait,
+    generics: Vec<&'a GenericParam>,
+}
+
+/// Preprocessed `contract` macro input for non-trait impl block
+pub struct ImplInput<'a> {
+    attributes: &'a ContractArgs,
+    item: &'a ItemImpl,
+    generics: Vec<&'a GenericParam>,
+}
+
+impl<'a> TraitInput<'a> {
+    pub fn new(attributes: &'a InterfaceArgs, item: &'a ItemTrait) -> Self {
+        let generics = item.generics.params.iter().collect();
+
+        if !item
+            .items
+            .iter()
+            .any(|item| matches!(item, TraitItem::Type(ty) if ty.ident == Ident::new("Error", ty.ident.span())))
+        {
+            emit_error!(
+                item.ident.span(), "Missing `Error` type defined for trait.";
+                note = "Error is an error type returned by generated types dispatch function. Messages handling function have to return an error type convertible to this Error type.";
+                note = "A trait error type should be bound to implement `From<cosmwasm_std::StdError>`.";
+            );
+        }
+
+        Self {
+            attributes,
+            item,
+            generics,
+        }
+    }
+
+    pub fn process(&self) -> TokenStream {
+        let messages = self.emit_messages();
+
+        if let Some(module) = &self.attributes.module {
+            quote! {
+                pub mod #module {
+                    use super::*;
+
+                    #messages
+                }
+            }
+        } else {
+            messages
+        }
+    }
+
+    fn emit_messages(&self) -> TokenStream {
+        let exec = self.emit_msg(
+            &Ident::new("ExecMsg", Span::mixed_site()),
+            MsgType::Exec,
+            self.attributes,
+        );
+        let query = self.emit_msg(
+            &Ident::new("QueryMsg", Span::mixed_site()),
+            MsgType::Query,
+            self.attributes,
+        );
+
+        quote! {
+            #exec
+
+            #query
+        }
+    }
+
+    fn emit_msg(&self, name: &Ident, msg_ty: MsgType, args: &InterfaceArgs) -> TokenStream {
+        EnumMessage::new(name, self.item, msg_ty, &self.generics, args).emit()
+    }
+}
+
+impl<'a> ImplInput<'a> {
+    pub fn new(attributes: &'a ContractArgs, item: &'a ItemImpl) -> Self {
+        let generics = item.generics.params.iter().collect();
+
+        Self {
+            attributes,
+            item,
+            generics,
+        }
+    }
+
+    pub fn process(&self) -> TokenStream {
+        let messages = self.emit_messages();
+
+        if let Some(module) = &self.attributes.module {
+            quote! {
+                pub mod #module {
+                    use super::*;
+
+                    #messages
+                }
+            }
+        } else {
+            messages
+        }
+    }
+
+    fn emit_messages(&self) -> TokenStream {
+        let instantiate = self.emit_msg(MsgType::Instantiate);
+        let exec = self.emit_glue_msg(&Ident::new("ExecMsg", Span::mixed_site()), MsgType::Exec);
+        let query = self.emit_glue_msg(&Ident::new("QueryMsg", Span::mixed_site()), MsgType::Query);
+
+        quote! {
+            #instantiate
+
+            #exec
+
+            #query
+        }
+    }
+
+    fn emit_msg(&self, msg_ty: MsgType) -> TokenStream {
+        StructMessage::new(self.item, msg_ty, &self.generics).map_or(quote! {}, |msg| msg.emit())
+    }
+
+    fn emit_glue_msg(&self, name: &Ident, msg_ty: MsgType) -> TokenStream {
+        GlueMessage::new(name, self.item, msg_ty, &self.attributes.error).emit()
+    }
+}
diff --git a/sylvia-derive/src/lib.rs b/sylvia-derive/src/lib.rs
new file mode 100644
index 0000000..9186b50
--- /dev/null
+++ b/sylvia-derive/src/lib.rs
@@ -0,0 +1,195 @@
+use input::{ImplInput, TraitInput};
+use proc_macro::TokenStream;
+use proc_macro_error::proc_macro_error;
+use quote::quote;
+use syn::fold::Fold;
+use syn::{parse_macro_input, ItemImpl, ItemTrait};
+
+pub(crate) mod check_generics;
+mod input;
+mod message;
+mod parser;
+mod strip_generics;
+mod strip_input;
+
+use strip_input::StripInput;
+
+/// Macro generating messages from contract trait.
+///
+/// ## Example usage
+/// ```ignore
+/// # use cosmwasm_std::Response;
+///
+/// # struct Ctx;
+/// # struct Error;
+///
+/// # #[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, schemars::JsonSchema)]
+/// # struct Member;
+///
+/// # #[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, schemars::JsonSchema)]
+/// # struct AdminQueryResponse;
+///
+/// # #[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, schemars::JsonSchema)]
+/// # struct MemberQueryResponse;
+///
+/// #[cw_derive::interface(module=msg, query=Query)]
+/// trait Cw4 {
+///     type Error: From<StdError>;
+///
+///     #[msg(exec)]
+///     fn update_admin(&self, ctx: (DepsMut, Env, MessageInfo), admin: Option<String>) -> Result<Response, Self::Error>;
+///
+///     #[msg(exec)]
+///     fn update_members(&self, ctx: (DepsMut, Env, MessageInfo), remove: Vec<String>, add: Vec<Member>)
+///         -> Result<Response, Self::Error>;
+///
+///     #[msg(query)]
+///     fn admin(&self, ctx: (Deps, Env)) -> Result<AdminQueryResponse, Error>;
+///
+///     #[msg(query)]
+///     fn member(&self, ctx: (Deps, Env), addr: String, at_height: Option<u64>) -> Result<MemberQueryResponse, Error>;
+/// }
+/// ```
+///
+/// This would generate output like:
+///
+/// ```ignore
+/// pub mod msg {
+///     # #[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, schemars::JsonSchema)]
+///     # struct Member;
+///
+///     #[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, schemars::JsonSchema)]
+///     #[serde(rename_all = "snake_case")]
+///     pub enum ExecMsg {
+///         UpdateAdmin { admin: Option<String> },
+///         UpdateMembers {
+///             remove: Vec<String>,
+///             add: Vec<Member>,
+///         },
+///         AddHook { addr: String },
+///         RemoveHook { addr: String },
+///     }
+///
+///     impl ExecMsg {
+///         pub fn dispatch<C: Cw4>(contract: &C, ctx: (DepsMut, Env, MessageInfo))
+///             -> Result<Response, C::Error>
+///         {
+///             // Some dispatching implementation
+///         }
+///     }
+/// }
+///
+/// And similar `Query` structure for handling queries.
+/// ```
+///
+/// ## Parameters
+///
+/// `interface` attribute takes optional parameters:
+/// * `module` - defines module name, where all generated messages would be encapsulated; no
+/// additional module would be created if not provided
+///
+/// ## Attributes
+///
+/// Messages structures are generated basing on interface trait method attributed with
+/// `#[msg(msg_type, ...)`. Msg attribute takes as its first argument type of message it is
+/// supposed to handle:
+///   * `exec` - this is execute message variant
+///   * `query` - this is query message variant
+///
+/// For now, `#[msg(...)]` attribute doesn't support any additional data on `#[interface]`
+/// elements, but it may be extended in future.
+#[proc_macro_error]
+#[proc_macro_attribute]
+pub fn interface(attr: TokenStream, item: TokenStream) -> TokenStream {
+    let attrs = parse_macro_input!(attr as parser::InterfaceArgs);
+    let input = parse_macro_input!(item as ItemTrait);
+
+    let expanded = TraitInput::new(&attrs, &input).process();
+    let input = StripInput.fold_item_trait(input);
+
+    let expanded = quote! {
+        #input
+
+        #expanded
+    };
+
+    TokenStream::from(expanded)
+}
+
+/// Macro generating messages from contract impl block.
+///
+/// ## Example usage
+/// ```ignore
+/// # use cosmwasm_std::Response;
+///
+/// # struct Ctx;
+/// # struct Error;
+///
+/// # #[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, schemars::JsonSchema)]
+/// # struct Cw4Group;
+///
+/// #[cw_derive::contract(module=msg)]
+/// impl Cw4Group {
+///     #[msg(instantiate, name="Instantiate")]
+///     fn instantiate(&self, ctx: (DepsMut, Env, MessageInfo), admin: Option<String>)
+///         -> Result<Response, Error>;
+/// }
+/// ```
+///
+/// This would generate output like:
+///
+/// ```ignore
+/// pub mod msg {
+///     # #[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, schemars::JsonSchema)]
+///     # struct Cw4Group;
+///
+///     #[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, schemars::JsonSchema)]
+///     #[serde(rename_all = "snake_case")]
+///     pub struct Instantiate {
+///         admin: Option<String>,
+///     }
+///
+///     impl Instantiate {
+///         fn dispatch(contract: &Cw4Group, ctx: (DepsMut, Env, MessageInfo), admin: Option<String>)
+///             -> Result<Response, Error>
+///         {
+///             contract.instantiate(ctx, admin)
+///         }
+///     }
+/// }
+/// ```
+///
+/// ## Parameters
+///
+/// `contract` attribute takes optional parameters:
+/// * `module` - defines module name, where all generated messages would be encapsulated; no
+/// additional module would be created if not provided
+///
+/// ## Attributes
+///
+/// Messages structures are generated basing on specific implemented methods attributed with
+/// `#[msg(msg_type, ...)`. Msg attribute takes as its first argument type of message it is
+/// supposed to handle:
+/// * `instantiate` - this is instantiation message handler. There should be always exactly one
+/// handler for this kind of message.
+///
+/// Additionally `#[msg(...)]` attribute handles additional arguments for contracts:
+/// * `name = "MessageName"` - overwrites generated message name. Valid for `instantiate` messages
+/// ("Instantiate" is default).
+#[proc_macro_error]
+#[proc_macro_attribute]
+pub fn contract(attr: TokenStream, item: TokenStream) -> TokenStream {
+    let attrs = parse_macro_input!(attr as parser::ContractArgs);
+    let input = parse_macro_input!(item as ItemImpl);
+
+    let expanded = ImplInput::new(&attrs, &input).process();
+    let input = StripInput.fold_item_impl(input);
+
+    let expanded = quote! {
+        #input
+
+        #expanded
+    };
+
+    TokenStream::from(expanded)
+}
diff --git a/sylvia-derive/src/message.rs b/sylvia-derive/src/message.rs
new file mode 100644
index 0000000..7929b13
--- /dev/null
+++ b/sylvia-derive/src/message.rs
@@ -0,0 +1,608 @@
+use crate::check_generics::CheckGenerics;
+use crate::parser::{ContractMessageAttr, InterfaceArgs, MsgAttr, MsgType};
+use crate::strip_generics::StripGenerics;
+use convert_case::{Case, Casing};
+use proc_macro2::{Span, TokenStream};
+use proc_macro_error::emit_error;
+use quote::quote;
+use syn::fold::Fold;
+use syn::parse::{Parse, Parser};
+use syn::spanned::Spanned;
+use syn::visit::Visit;
+use syn::{
+    parse_quote, FnArg, GenericParam, Ident, ImplItem, ItemImpl, ItemTrait, Pat, PatType,
+    ReturnType, Signature, TraitItem, TraitItemMethod, Type, WhereClause, WherePredicate,
+};
+
+fn filter_wheres<'a>(
+    clause: &'a Option<WhereClause>,
+    generics: &[&GenericParam],
+    used_generics: &[&GenericParam],
+) -> Vec<&'a WherePredicate> {
+    clause
+        .as_ref()
+        .map(|clause| {
+            clause
+                .predicates
+                .iter()
+                .filter(|pred| {
+                    let mut generics_checker = CheckGenerics::new(generics);
+                    generics_checker.visit_where_predicate(pred);
+                    generics_checker
+                        .used()
+                        .into_iter()
+                        .all(|gen| used_generics.contains(&gen))
+                })
+                .collect()
+        })
+        .unwrap_or_default()
+}
+
+fn process_fields<'s>(
+    sig: &'s Signature,
+    generics_checker: &mut CheckGenerics,
+) -> Vec<MsgField<'s>> {
+    sig.inputs
+        .iter()
+        .skip(2)
+        .filter_map(|arg| match arg {
+            FnArg::Receiver(item) => {
+                emit_error!(item.span(), "Unexpected `self` argument");
+                None
+            }
+
+            FnArg::Typed(item) => MsgField::new(item, generics_checker),
+        })
+        .collect()
+}
+
+/// Representation of single struct message
+pub struct StructMessage<'a> {
+    contract_type: &'a Type,
+    fields: Vec<MsgField<'a>>,
+    function_name: &'a Ident,
+    generics: Vec<&'a GenericParam>,
+    unused_generics: Vec<&'a GenericParam>,
+    wheres: Vec<&'a WherePredicate>,
+    full_where: Option<&'a WhereClause>,
+    result: &'a ReturnType,
+    msg_attr: MsgAttr,
+}
+
+impl<'a> StructMessage<'a> {
+    /// Creates new struct message of given type from impl block
+    pub fn new(
+        source: &'a ItemImpl,
+        ty: MsgType,
+        generics: &'a [&'a GenericParam],
+    ) -> Option<StructMessage<'a>> {
+        let mut generics_checker = CheckGenerics::new(generics);
+
+        let contract_type = &source.self_ty;
+        let mut methods = source.items.iter().filter_map(|item| match item {
+            ImplItem::Method(method) => {
+                let msg_attr = method.attrs.iter().find(|attr| attr.path.is_ident("msg"))?;
+                let attr = match MsgAttr::parse.parse2(msg_attr.tokens.clone()) {
+                    Ok(attr) => attr,
+                    Err(err) => {
+                        emit_error!(method.span(), err);
+                        return None;
+                    }
+                };
+
+                if attr == ty {
+                    Some((method, attr))
+                } else {
+                    None
+                }
+            }
+            _ => None,
+        });
+
+        let (method, msg_attr) = if let Some(method) = methods.next() {
+            method
+        } else {
+            emit_error!(source.span(), "No instantiation message");
+            return None;
+        };
+
+        if let Some((obsolete, _)) = methods.next() {
+            emit_error!(
+                obsolete.span(), "More than one instantiation message";
+                note = method.span() => "Instantiation message previously defied here"
+            );
+        }
+
+        let function_name = &method.sig.ident;
+        let fields = process_fields(&method.sig, &mut generics_checker);
+        let (used_generics, unused_generics) = generics_checker.used_unused();
+        let wheres = filter_wheres(&source.generics.where_clause, generics, &used_generics);
+
+        Some(Self {
+            contract_type,
+            fields,
+            function_name,
+            generics: used_generics,
+            unused_generics,
+            wheres,
+            full_where: source.generics.where_clause.as_ref(),
+            result: &method.sig.output,
+            msg_attr,
+        })
+    }
+
+    pub fn emit(&self) -> TokenStream {
+        use MsgAttr::*;
+
+        match &self.msg_attr {
+            Instantiate { name } => self.emit_struct(name),
+            _ => {
+                emit_error!(Span::mixed_site(), "Invalid message type");
+                quote! {}
+            }
+        }
+    }
+
+    pub fn emit_struct(&self, name: &Ident) -> TokenStream {
+        let Self {
+            contract_type,
+            fields,
+            function_name,
+            generics,
+            unused_generics,
+            wheres,
+            full_where,
+            result,
+            msg_attr,
+        } = self;
+
+        let where_clause = if !wheres.is_empty() {
+            quote! {
+                where #(#wheres,)*
+            }
+        } else {
+            quote! {}
+        };
+
+        let ctx_type = msg_attr.msg_type().emit_ctx_type();
+        let fields_names: Vec<_> = fields.iter().map(MsgField::name).collect();
+        let fields = fields.iter().map(MsgField::emit);
+
+        let generics = if generics.is_empty() {
+            quote! {}
+        } else {
+            quote! {
+                <#(#generics,)*>
+            }
+        };
+
+        let unused_generics = if unused_generics.is_empty() {
+            quote! {}
+        } else {
+            quote! {
+                <#(#unused_generics,)*>
+            }
+        };
+
+        quote! {
+            #[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, schemars::JsonSchema)]
+            #[serde(rename_all="snake_case")]
+            pub struct #name #generics #where_clause {
+                #(pub #fields,)*
+            }
+
+            impl #generics #name #generics #where_clause {
+                pub fn dispatch #unused_generics(self, contract: &#contract_type, ctx: #ctx_type)
+                    #result #full_where
+                {
+                    let Self { #(#fields_names,)* } = self;
+                    contract.#function_name(ctx.into(), #(#fields_names,)*).map_err(Into::into)
+                }
+            }
+        }
+    }
+}
+
+/// Representation of single enum message
+pub struct EnumMessage<'a> {
+    name: &'a Ident,
+    trait_name: &'a Ident,
+    variants: Vec<MsgVariant<'a>>,
+    generics: Vec<&'a GenericParam>,
+    unused_generics: Vec<&'a GenericParam>,
+    all_generics: &'a [&'a GenericParam],
+    wheres: Vec<&'a WherePredicate>,
+    full_where: Option<&'a WhereClause>,
+    msg_ty: MsgType,
+    args: &'a InterfaceArgs,
+}
+
+impl<'a> EnumMessage<'a> {
+    pub fn new(
+        name: &'a Ident,
+        source: &'a ItemTrait,
+        ty: MsgType,
+        generics: &'a [&'a GenericParam],
+        args: &'a InterfaceArgs,
+    ) -> Self {
+        let trait_name = &source.ident;
+
+        let mut generics_checker = CheckGenerics::new(generics);
+        let variants: Vec<_> = source
+            .items
+            .iter()
+            .filter_map(|item| match item {
+                TraitItem::Method(method) => {
+                    let msg_attr = method.attrs.iter().find(|attr| attr.path.is_ident("msg"))?;
+                    let attr = match MsgAttr::parse.parse2(msg_attr.tokens.clone()) {
+                        Ok(attr) => attr,
+                        Err(err) => {
+                            emit_error!(method.span(), err);
+                            return None;
+                        }
+                    };
+
+                    if attr == ty {
+                        Some(MsgVariant::new(method, &mut generics_checker))
+                    } else {
+                        None
+                    }
+                }
+                _ => None,
+            })
+            .collect();
+
+        let (used_generics, unused_generics) = generics_checker.used_unused();
+        let wheres = filter_wheres(&source.generics.where_clause, generics, &used_generics);
+
+        Self {
+            name,
+            trait_name,
+            variants,
+            generics: used_generics,
+            unused_generics,
+            all_generics: generics,
+            wheres,
+            full_where: source.generics.where_clause.as_ref(),
+            msg_ty: ty,
+            args,
+        }
+    }
+
+    pub fn emit(&self) -> TokenStream {
+        let Self {
+            name,
+            trait_name,
+            variants,
+            generics,
+            unused_generics,
+            all_generics,
+            wheres,
+            full_where,
+            msg_ty,
+            args,
+        } = self;
+
+        let match_arms = variants
+            .iter()
+            .map(|variant| variant.emit_dispatch_leg(*msg_ty));
+        let variants = variants.iter().map(MsgVariant::emit);
+        let where_clause = if !wheres.is_empty() {
+            quote! {
+                where #(#wheres,)*
+            }
+        } else {
+            quote! {}
+        };
+
+        let ctx_type = msg_ty.emit_ctx_type();
+        let dispatch_type = msg_ty.emit_result_type(&args.msg_type, &parse_quote!(C::Error));
+
+        let all_generics = if all_generics.is_empty() {
+            quote! {}
+        } else {
+            quote! { <#(#all_generics,)*> }
+        };
+
+        let generics = if generics.is_empty() {
+            quote! {}
+        } else {
+            quote! { <#(#generics,)*> }
+        };
+
+        quote! {
+            #[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, schemars::JsonSchema)]
+            #[serde(rename_all="snake_case")]
+            pub enum #name #generics #where_clause {
+                #(#variants,)*
+            }
+
+            impl #generics #name #generics #where_clause {
+                pub fn dispatch<C: #trait_name #all_generics, #(#unused_generics,)*>(self, contract: &C, ctx: #ctx_type)
+                    -> #dispatch_type #full_where
+                {
+                    use #name::*;
+
+                    match self {
+                        #(#match_arms,)*
+                    }
+                }
+            }
+        }
+    }
+}
+
+/// Representation of whole message variant
+pub struct MsgVariant<'a> {
+    name: Ident,
+    function_name: &'a Ident,
+    // With https://github.com/rust-lang/rust/issues/63063 this could be just an iterator over
+    // `MsgField<'a>`
+    fields: Vec<MsgField<'a>>,
+}
+
+impl<'a> MsgVariant<'a> {
+    /// Creates new message variant from trait method
+    pub fn new(
+        method: &'a TraitItemMethod,
+        generics_checker: &mut CheckGenerics,
+    ) -> MsgVariant<'a> {
+        let function_name = &method.sig.ident;
+        let name = Ident::new(
+            &function_name.to_string().to_case(Case::UpperCamel),
+            function_name.span(),
+        );
+        let fields = process_fields(&method.sig, generics_checker);
+
+        Self {
+            name,
+            function_name,
+            fields,
+        }
+    }
+
+    /// Emits message variant
+    pub fn emit(&self) -> TokenStream {
+        let Self { name, fields, .. } = self;
+        let fields = fields.iter().map(MsgField::emit);
+
+        quote! {
+            #name {
+                #(#fields,)*
+            }
+        }
+    }
+
+    /// Emits match leg dispatching against this variant. Assumes enum variants are imported into the
+    /// scope. Dispatching is performed by calling the function this variant is build from on the
+    /// `contract` variable, with `ctx` as its first argument - both of them should be in scope.
+    pub fn emit_dispatch_leg(&self, msg_attr: MsgType) -> TokenStream {
+        use MsgType::*;
+
+        let Self {
+            name,
+            fields,
+            function_name,
+        } = self;
+        let args = fields.iter().map(|field| field.name);
+        let fields = fields.iter().map(|field| field.name);
+
+        match msg_attr {
+            Exec => quote! {
+                #name {
+                    #(#fields,)*
+                } => contract.#function_name(ctx.into(), #(#args),*).map_err(Into::into)
+            },
+            Query => quote! {
+                #name {
+                    #(#fields,)*
+                } => cosmwasm_std::to_binary(&contract.#function_name(ctx.into(), #(#args),*)?).map_err(Into::into)
+            },
+            Instantiate => {
+                emit_error!(name.span(), "Instantiation messages not supported on traits, they should be defined on contracts directly");
+                quote! {}
+            }
+        }
+    }
+}
+
+/// Representation of single message variant field
+pub struct MsgField<'a> {
+    name: &'a Ident,
+    ty: &'a Type,
+}
+
+impl<'a> MsgField<'a> {
+    /// Creates new field from trait method argument
+    pub fn new(item: &'a PatType, generics_checker: &mut CheckGenerics) -> Option<MsgField<'a>> {
+        let name = match &*item.pat {
+            Pat::Ident(p) => Some(&p.ident),
+            pat => {
+                // TODO: Support pattern arguments, when decorated with argument with item
+                // name
+                //
+                // Eg.
+                //
+                // ```
+                // fn exec_foo(&self, ctx: Ctx, #[msg(name=metadata)] SomeData { addr, sender }: SomeData);
+                // ```
+                //
+                // should expand to enum variant:
+                //
+                // ```
+                // ExecFoo {
+                //   metadata: SomeDaa
+                // }
+                // ```
+                emit_error!(pat.span(), "Expected argument name, pattern occurred");
+                None
+            }
+        }?;
+
+        let ty = &item.ty;
+        generics_checker.visit_type(ty);
+
+        Some(Self { name, ty })
+    }
+
+    /// Emits message field
+    pub fn emit(&self) -> TokenStream {
+        let Self { name, ty } = self;
+
+        quote! {
+            #name: #ty
+        }
+    }
+
+    pub fn name(&self) -> &'a Ident {
+        self.name
+    }
+}
+
+// pub struct EnumMessage<'a> {
+//     name: &'a Ident,
+//     trait_name: &'a Ident,
+//     variants: Vec<MsgVariant<'a>>,
+//     generics: Vec<&'a GenericParam>,
+//     unused_generics: Vec<&'a GenericParam>,
+//     all_generics: &'a [&'a GenericParam],
+//     wheres: Vec<&'a WherePredicate>,
+//     full_where: Option<&'a WhereClause>,
+//     msg_ty: MsgType,
+//     args: &'a InterfaceArgs,
+// }
+
+/// Glue message is the message composing Exec/Query messages from several traits
+#[derive(Debug)]
+pub struct GlueMessage<'a> {
+    interfaces: Vec<ContractMessageAttr>,
+    name: &'a Ident,
+    contract: &'a Type,
+    msg_ty: MsgType,
+    error: &'a Type,
+}
+
+impl<'a> GlueMessage<'a> {
+    pub fn new(name: &'a Ident, source: &'a ItemImpl, msg_ty: MsgType, error: &'a Type) -> Self {
+        let interfaces: Vec<_> = source
+            .attrs
+            .iter()
+            .filter(|attr| attr.path.is_ident("messages"))
+            .filter_map(|attr| {
+                let interface = match ContractMessageAttr::parse.parse2(attr.tokens.clone()) {
+                    Ok(interface) => interface,
+                    Err(err) => {
+                        emit_error!(attr.span(), err);
+                        return None;
+                    }
+                };
+
+                Some(interface)
+            })
+            .collect();
+
+        GlueMessage {
+            interfaces,
+            name,
+            contract: &source.self_ty,
+            msg_ty,
+            error,
+        }
+    }
+
+    pub fn emit(&self) -> TokenStream {
+        let Self {
+            interfaces,
+            name,
+            contract,
+            msg_ty,
+            error,
+        } = self;
+        let contract = StripGenerics.fold_type((*contract).clone());
+
+        let variants = interfaces.iter().map(|interface| {
+            let ContractMessageAttr {
+                module,
+                exec_generic_params,
+                query_generic_params,
+                variant,
+            } = interface;
+
+            let generics = match msg_ty {
+                MsgType::Exec => exec_generic_params.as_slice(),
+                MsgType::Query => query_generic_params.as_slice(),
+                _ => &[],
+            };
+
+            quote! { #variant(#module :: #name<#(#generics,)*>) }
+        });
+
+        let dispatch_arms = interfaces.iter().map(|interface| {
+            let ContractMessageAttr { variant, .. } = interface;
+
+            quote! { #name :: #variant(msg) => msg.dispatch(contract, ctx) }
+        });
+
+        let deserialization_attempts = interfaces.iter().map(|interface| {
+            let ContractMessageAttr { variant, .. } = interface;
+            let var = Ident::new(&variant.to_string().to_case(Case::Snake), variant.span());
+
+            quote! {
+                let #var = match val.clone().deserialize_into() {
+                    Ok(msg) => return Ok(Self:: #variant (msg)),
+                    Err(err) => err,
+                };
+            }
+        });
+
+        let deser_errors = interfaces.iter().map(|interface| {
+            let ContractMessageAttr { variant, .. } = interface;
+            let var = Ident::new(&variant.to_string().to_case(Case::Snake), variant.span());
+
+            quote! { format!("As {}: {}", stringify!(#variant), #var) }
+        });
+
+        let messages_type = interfaces.iter().map(|interface| &interface.variant);
+
+        let ctx_type = msg_ty.emit_ctx_type();
+        let ret_type = msg_ty.emit_result_type(&None, error);
+
+        quote! {
+            #[derive(serde::Serialize, Clone, Debug, PartialEq, schemars::JsonSchema)]
+            #[serde(rename_all="snake_case")]
+            pub enum #name {
+                #(#variants,)*
+            }
+
+            impl #name {
+                pub fn dispatch(
+                    self,
+                    contract: &#contract,
+                    ctx: #ctx_type,
+                ) -> #ret_type {
+                    match self {
+                        #(#dispatch_arms,)*
+                    }
+                }
+
+            }
+
+            impl<'de> serde::Deserialize<'de> for #name {
+                fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+                    where D: serde::Deserializer<'de>,
+                {
+                    use serde::de::Error;
+                    let val = serde_cw_value::Value::deserialize(deserializer)?;
+
+                    #(#deserialization_attempts)*
+
+                    Err(D::Error::custom(format!(
+                        "Expected any of {} messages, but cannot deserialize to neither of those\n{}",
+                        stringify!(#(#messages_type),*),
+                        [#(#deser_errors,)*].join("\n")
+                    )))
+                }
+            }
+        }
+    }
+}
diff --git a/sylvia-derive/src/parser.rs b/sylvia-derive/src/parser.rs
new file mode 100644
index 0000000..a8064d2
--- /dev/null
+++ b/sylvia-derive/src/parser.rs
@@ -0,0 +1,302 @@
+use proc_macro2::TokenStream;
+use quote::quote;
+use syn::parse::{Error, Nothing, Parse, ParseBuffer, ParseStream};
+use syn::punctuated::Punctuated;
+use syn::{parenthesized, parse2, parse_quote, Ident, Path, Result, Token, Type};
+
+/// Parsed arguments for `interface` macro
+pub struct InterfaceArgs {
+    /// Module name wrapping generated messages, by default no additional module is created
+    pub module: Option<Ident>,
+    /// The type being a parameter of `CosmosMsg` for blockchain it is intendet to be used; can be
+    /// set to any of generic parameters to create interface being generic over blockchains; If not
+    /// provided, cosmos messages would be unparametrized (so default would be used)
+    pub msg_type: Option<Type>,
+}
+
+/// Parser arguments for `contract` macro
+pub struct ContractArgs {
+    /// Module name wrapping generated messages, by default no additional module is created
+    pub module: Option<Ident>,
+    /// The type of a contract error for entry points - `ContractError` by default
+    pub error: Type,
+}
+
+struct Mapping {
+    index: Ident,
+    value: TokenStream,
+}
+
+impl Parse for InterfaceArgs {
+    fn parse(input: ParseStream) -> Result<Self> {
+        let mut module = None;
+        let mut msg_type = None;
+
+        while !input.is_empty() {
+            let attr: Ident = input.parse()?;
+            let _: Token![=] = input.parse()?;
+
+            if attr == "module" {
+                module = Some(input.parse()?);
+            } else if attr == "msg_type" {
+                msg_type = Some(input.parse()?);
+            } else {
+                return Err(Error::new(attr.span(), "expected `module` or `msg_type`"));
+            }
+
+            if input.peek(Token![,]) {
+                let _: Token![,] = input.parse()?;
+            } else if !input.is_empty() {
+                return Err(input.error("Unexpected token, comma expected"));
+            }
+        }
+
+        let _: Nothing = input.parse()?;
+
+        Ok(InterfaceArgs { module, msg_type })
+    }
+}
+
+impl Parse for ContractArgs {
+    fn parse(input: ParseStream) -> Result<Self> {
+        let mut module = None;
+        let mut error = parse_quote!(ContractError);
+
+        while !input.is_empty() {
+            let attr: Ident = input.parse()?;
+            let _: Token![=] = input.parse()?;
+
+            if attr == "module" {
+                module = Some(input.parse()?);
+            } else if attr == "error" {
+                error = input.parse()?;
+            } else {
+                return Err(Error::new(attr.span(), "expected `module` or `error`"));
+            }
+
+            if input.peek(Token![,]) {
+                let _: Token![,] = input.parse()?;
+            } else if !input.is_empty() {
+                return Err(input.error("Unexpected token, comma expected"));
+            }
+        }
+
+        let _: Nothing = input.parse()?;
+
+        Ok(ContractArgs { module, error })
+    }
+}
+
+impl Parse for Mapping {
+    fn parse(input: ParseStream) -> Result<Self> {
+        let index = input.parse()?;
+        input.parse::<Token![=]>()?;
+        let value = input.parse()?;
+        Ok(Mapping { index, value })
+    }
+}
+
+/// Type of message to be generated
+#[derive(PartialEq, Debug, Clone, Copy)]
+pub enum MsgType {
+    Exec,
+    Query,
+    Instantiate,
+}
+
+/// `#[msg(...)]` attribute for `interface` macro
+pub enum MsgAttr {
+    Exec,
+    Query,
+    Instantiate { name: Ident },
+}
+
+impl MsgType {
+    pub fn emit_ctx_type(self) -> TokenStream {
+        use MsgType::*;
+
+        match self {
+            Exec | Instantiate => quote! {
+                (cosmwasm_std::DepsMut, cosmwasm_std::Env, cosmwasm_std::MessageInfo)
+            },
+            Query => quote! {
+                (cosmwasm_std::Deps, cosmwasm_std::Env)
+            },
+        }
+    }
+
+    /// Emits type which should be returned by dispatch function for this kind of message
+    pub fn emit_result_type(self, msg_type: &Option<Type>, err_type: &Type) -> TokenStream {
+        use MsgType::*;
+
+        match (self, msg_type) {
+            (Exec, Some(msg_type)) | (Instantiate, Some(msg_type)) => quote! {
+                std::result::Result<cosmwasm_std::Response<#msg_type>, #err_type>
+            },
+            (Exec, None) | (Instantiate, None) => quote! {
+                std::result::Result<cosmwasm_std::Response, #err_type>
+            },
+
+            (Query, _) => quote! {
+                std::result::Result<cosmwasm_std::Binary, #err_type>
+            },
+        }
+    }
+}
+
+impl Parse for MsgType {
+    fn parse(input: ParseStream) -> Result<Self> {
+        use MsgType::*;
+
+        let item: Ident = input.parse()?;
+        if item == "exec" {
+            Ok(Exec)
+        } else if item == "query" {
+            Ok(Query)
+        } else if item == "instantiate" {
+            Ok(Instantiate)
+        } else {
+            Err(Error::new(
+                item.span(),
+                "Invalid message type, expected one of: `exec`, `query`, `instantiate`",
+            ))
+        }
+    }
+}
+
+impl PartialEq<MsgType> for MsgAttr {
+    fn eq(&self, other: &MsgType) -> bool {
+        self.msg_type() == *other
+    }
+}
+
+impl MsgAttr {
+    fn parse_instantiate(content: ParseBuffer) -> Result<Self> {
+        let mut name = Ident::new("InstantiateMsg", content.span());
+        let p: Option<Token![,]> = content.parse()?;
+
+        if p.is_some() {
+            let attrs: Punctuated<Mapping, Token![,]> = content.parse_terminated(Mapping::parse)?;
+            for attr in attrs {
+                if attr.index == "name" {
+                    name = parse2(attr.value)?;
+                }
+            }
+        }
+
+        Ok(Self::Instantiate { name })
+    }
+
+    pub fn msg_type(&self) -> MsgType {
+        use MsgAttr::*;
+
+        match self {
+            Exec => MsgType::Exec,
+            Query => MsgType::Query,
+            Instantiate { .. } => MsgType::Instantiate,
+        }
+    }
+}
+
+impl Parse for MsgAttr {
+    fn parse(input: ParseStream) -> Result<Self> {
+        let content;
+        parenthesized!(content in input);
+
+        let ty: Ident = content.parse()?;
+        if ty == "exec" {
+            Ok(Self::Exec)
+        } else if ty == "query" {
+            Ok(Self::Query)
+        } else if ty == "instantiate" {
+            Self::parse_instantiate(content)
+        } else {
+            Err(Error::new(
+                ty.span(),
+                "Invalid message type, expected one of: `exec`, `query`, `instantiate`",
+            ))
+        }
+    }
+}
+
+#[derive(Debug)]
+pub struct ContractMessageAttr {
+    pub module: Path,
+    pub exec_generic_params: Vec<Path>,
+    pub query_generic_params: Vec<Path>,
+    pub variant: Ident,
+}
+
+fn parse_generics(content: &ParseBuffer) -> Result<Vec<Path>> {
+    let _: Token![<] = content.parse()?;
+    let mut params = vec![];
+
+    loop {
+        let param: Path = content.parse()?;
+        params.push(param);
+
+        let generics_close: Option<Token![>]> = content.parse()?;
+        if generics_close.is_some() {
+            break;
+        }
+
+        let comma: Option<Token![,]> = content.parse()?;
+        if comma.is_none() {
+            return Err(Error::new(content.span(), "Expected comma or `>`"));
+        }
+    }
+
+    Ok(params)
+}
+
+impl Parse for ContractMessageAttr {
+    fn parse(input: ParseStream) -> Result<Self> {
+        let content;
+        parenthesized!(content in input);
+
+        let module = content.parse()?;
+
+        let generics_open: Option<Token![:]> = content.parse()?;
+        let mut exec_generic_params = vec![];
+        let mut query_generic_params = vec![];
+
+        if generics_open.is_some() {
+            loop {
+                let ty: Ident = content.parse()?;
+                let params = if ty == "exec" {
+                    &mut exec_generic_params
+                } else if ty == "query" {
+                    &mut query_generic_params
+                } else {
+                    return Err(Error::new(ty.span(), "Invalid message type"));
+                };
+
+                *params = parse_generics(&content)?;
+
+                if content.peek(Token![as]) {
+                    break;
+                }
+
+                let _: Token![,] = content.parse()?;
+            }
+        }
+
+        let _: Token![as] = content.parse()?;
+
+        let variant = content.parse()?;
+
+        if !content.is_empty() {
+            return Err(Error::new(
+                content.span(),
+                "Unexpected token on the end of `message` attribtue",
+            ));
+        }
+
+        Ok(Self {
+            module,
+            exec_generic_params,
+            query_generic_params,
+            variant,
+        })
+    }
+}
diff --git a/sylvia-derive/src/strip_generics.rs b/sylvia-derive/src/strip_generics.rs
new file mode 100644
index 0000000..7681d51
--- /dev/null
+++ b/sylvia-derive/src/strip_generics.rs
@@ -0,0 +1,10 @@
+use syn::fold::Fold;
+
+/// Removes all generics from type assumning default values for it
+pub struct StripGenerics;
+
+impl Fold for StripGenerics {
+    fn fold_path_arguments(&mut self, _: syn::PathArguments) -> syn::PathArguments {
+        syn::PathArguments::None
+    }
+}
diff --git a/sylvia-derive/src/strip_input.rs b/sylvia-derive/src/strip_input.rs
new file mode 100644
index 0000000..666ed48
--- /dev/null
+++ b/sylvia-derive/src/strip_input.rs
@@ -0,0 +1,42 @@
+use syn::fold::{self, Fold};
+use syn::{ImplItemMethod, ItemImpl, TraitItemMethod};
+
+/// Utility for stripping all attributes from input before it is emitted
+pub struct StripInput;
+
+impl Fold for StripInput {
+    fn fold_trait_item_method(&mut self, i: TraitItemMethod) -> TraitItemMethod {
+        let attrs = i
+            .attrs
+            .into_iter()
+            .filter(|attr| !attr.path.is_ident("msg"));
+
+        fold::fold_trait_item_method(
+            self,
+            TraitItemMethod {
+                attrs: attrs.collect(),
+                ..i
+            },
+        )
+    }
+
+    fn fold_impl_item_method(&mut self, i: ImplItemMethod) -> ImplItemMethod {
+        let attrs = i
+            .attrs
+            .into_iter()
+            .filter(|attr| !attr.path.is_ident("msg"))
+            .collect();
+
+        fold::fold_impl_item_method(self, ImplItemMethod { attrs, ..i })
+    }
+
+    fn fold_item_impl(&mut self, i: ItemImpl) -> ItemImpl {
+        let attrs = i
+            .attrs
+            .into_iter()
+            .filter(|attr| !attr.path.is_ident("messages"))
+            .collect();
+
+        fold::fold_item_impl(self, ItemImpl { attrs, ..i })
+    }
+}
diff --git a/sylvia/Cargo.toml b/sylvia/Cargo.toml
new file mode 100644
index 0000000..4799146
--- /dev/null
+++ b/sylvia/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+name = "sylvia"
+version = "0.1.1"
+authors = ["Bartłomiej Kuras <bart.k@confio.gmbh>"]
+edition = "2021"
+description = "Framework for generating trait based CosmWasm smart contracts"
+license = "Apache-2.0"
+repository = "https://github.com/CosmWasm/sylvia"
+homepage = "https://cosmwasm.com"
+keywords = ["cosmwasm", "blockchain", "confio"]
+categories = ["api-bindings", "wasm"]
+
+
+[dependencies]
+sylvia-derive = { path = "../sylvia-derive" }
+cosmwasm-std = { version = "1", features = ["staking"] }
+schemars = "0.8.10"
+serde = { version = "1", default-features = false, features = ["derive"] }
+serde-cw-value = { version = "0.7.0", git = "https://github.com/hashedone/serde-cw-value.git" }
diff --git a/sylvia/src/lib.rs b/sylvia/src/lib.rs
new file mode 100644
index 0000000..4fd04c8
--- /dev/null
+++ b/sylvia/src/lib.rs
@@ -0,0 +1,9 @@
+//! Framework for creating CosmWasm Smart Contract with high-level abstraction layer
+//!
+//! Most of implementation lies in `cw-derive-ng` crate which is reexported here
+
+pub use cosmwasm_std as cw_std;
+pub use schemars;
+pub use serde;
+pub use serde_cw_value as serde_value;
+pub use sylvia_derive::{contract, interface};


Test patch:
diff --git a/sylvia-derive/tests/dispatching.rs b/sylvia-derive/tests/dispatching.rs
new file mode 100644
index 0000000..7c0b640
--- /dev/null
+++ b/sylvia-derive/tests/dispatching.rs
@@ -0,0 +1,140 @@
+use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
+use cosmwasm_std::{
+    from_binary, Addr, Decimal, Deps, DepsMut, Env, MessageInfo, Response, StdError,
+};
+use schemars::JsonSchema;
+use serde::{Deserialize, Serialize};
+use std::cell::RefCell;
+use std::collections::HashMap;
+
+use sylvia_derive::interface;
+
+#[interface]
+pub trait Interface {
+    type Error: From<StdError>;
+
+    #[msg(exec)]
+    fn no_args_execution(&self, ctx: (DepsMut, Env, MessageInfo)) -> Result<Response, Self::Error>;
+
+    #[msg(exec)]
+    fn argumented_execution(
+        &self,
+        ctx: (DepsMut, Env, MessageInfo),
+        addr: Addr,
+        coef: Decimal,
+        desc: String,
+    ) -> Result<Response, Self::Error>;
+
+    #[msg(query)]
+    fn no_args_query(&self, ctx: (Deps, Env)) -> Result<EmptyQueryResponse, Self::Error>;
+
+    #[msg(query)]
+    fn argumented_query(&self, ctx: (Deps, Env), user: Addr) -> Result<QueryResponse, Self::Error>;
+}
+
+#[derive(Default)]
+struct Contract {
+    execs: RefCell<u64>,
+    queries: RefCell<u64>,
+
+    data: RefCell<HashMap<Addr, QueryResponse>>,
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub struct EmptyQueryResponse {}
+
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub struct QueryResponse {
+    coef: Decimal,
+    desc: String,
+}
+
+impl Interface for Contract {
+    type Error = StdError;
+
+    fn no_args_execution(&self, _: (DepsMut, Env, MessageInfo)) -> Result<Response, StdError> {
+        *self.execs.borrow_mut() += 1;
+        Ok(Response::new())
+    }
+
+    fn argumented_execution(
+        &self,
+        _: (DepsMut, Env, MessageInfo),
+        addr: Addr,
+        coef: Decimal,
+        desc: String,
+    ) -> Result<Response, Self::Error> {
+        *self.execs.borrow_mut() += 1;
+
+        self.data
+            .borrow_mut()
+            .insert(addr, QueryResponse { coef, desc });
+        Ok(Response::new())
+    }
+
+    fn no_args_query(&self, _: (Deps, Env)) -> Result<EmptyQueryResponse, StdError> {
+        *self.queries.borrow_mut() += 1;
+        Ok(dbg!(EmptyQueryResponse {}))
+    }
+
+    fn argumented_query(&self, _: (Deps, Env), user: Addr) -> Result<QueryResponse, Self::Error> {
+        *self.queries.borrow_mut() += 1;
+        Ok(self.data.borrow().get(&user).unwrap().clone())
+    }
+}
+
+#[test]
+fn dispatch() {
+    let contract = Contract::default();
+
+    let mut deps = mock_dependencies();
+    let env = mock_env();
+    let info = mock_info("owner", &[]);
+
+    let resp = ExecMsg::NoArgsExecution {}
+        .dispatch(&contract, (deps.as_mut(), env.clone(), info.clone()))
+        .unwrap();
+    assert_eq!(resp, Response::new());
+
+    let resp = ExecMsg::ArgumentedExecution {
+        addr: Addr::unchecked("addr1"),
+        coef: Decimal::percent(30),
+        desc: "True".to_owned(),
+    }
+    .dispatch(&contract, (deps.as_mut(), env.clone(), info.clone()))
+    .unwrap();
+    assert_eq!(resp, Response::new());
+
+    let resp = ExecMsg::ArgumentedExecution {
+        addr: Addr::unchecked("addr2"),
+        coef: Decimal::percent(70),
+        desc: "False".to_owned(),
+    }
+    .dispatch(&contract, (deps.as_mut(), env.clone(), info))
+    .unwrap();
+    assert_eq!(resp, Response::new());
+
+    let resp = QueryMsg::NoArgsQuery {}
+        .dispatch(&contract, (deps.as_ref(), env.clone()))
+        .unwrap();
+    let _resp: EmptyQueryResponse = from_binary(&resp).unwrap();
+
+    let resp = QueryMsg::ArgumentedQuery {
+        user: Addr::unchecked("addr2"),
+    }
+    .dispatch(&contract, (deps.as_ref(), env))
+    .unwrap();
+    let resp: QueryResponse = from_binary(&resp).unwrap();
+    assert_eq!(
+        resp,
+        QueryResponse {
+            coef: Decimal::percent(70),
+            desc: "False".to_owned()
+        }
+    );
+
+    assert_eq!(*contract.execs.borrow(), 3);
+    assert_eq!(*contract.queries.borrow(), 2);
+}
diff --git a/sylvia-derive/tests/generics_dispatching.rs b/sylvia-derive/tests/generics_dispatching.rs
new file mode 100644
index 0000000..17e9a8c
--- /dev/null
+++ b/sylvia-derive/tests/generics_dispatching.rs
@@ -0,0 +1,165 @@
+use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
+use cosmwasm_std::{
+    from_binary, Addr, Deps, DepsMut, Env, MessageInfo, Response, StdError, Uint128,
+};
+use schemars::JsonSchema;
+use serde::{Deserialize, Serialize};
+use std::cell::RefCell;
+use std::collections::HashMap;
+
+use sylvia_derive::interface;
+
+#[interface]
+pub trait Interface<T>
+where
+    T: Serialize,
+{
+    type Error: From<StdError>;
+
+    #[msg(exec)]
+    fn store(
+        &self,
+        ctx: (DepsMut, Env, MessageInfo),
+        addr: Addr,
+        data: T,
+    ) -> Result<Response, Self::Error>;
+
+    #[msg(query)]
+    fn load(&self, ctx: (Deps, Env), addr: Addr) -> Result<QueryResponse<T>, Self::Error>;
+}
+
+#[derive(Debug, PartialEq)]
+pub enum Error {
+    Std(StdError),
+    MissingData(Addr),
+}
+
+impl From<StdError> for Error {
+    fn from(src: StdError) -> Self {
+        Self::Std(src)
+    }
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub struct QueryResponse<T> {
+    data: T,
+}
+
+#[derive(Default)]
+pub struct Contract<T> {
+    data: RefCell<HashMap<Addr, T>>,
+}
+
+impl<T> Interface<T> for Contract<T>
+where
+    T: Serialize + Clone,
+{
+    type Error = Error;
+
+    fn store(
+        &self,
+        _: (DepsMut, Env, MessageInfo),
+        addr: Addr,
+        data: T,
+    ) -> Result<Response, Error> {
+        self.data.borrow_mut().insert(addr, data);
+        Ok(Response::new())
+    }
+
+    fn load(&self, _: (Deps, Env), addr: Addr) -> Result<QueryResponse<T>, Error> {
+        let data = self
+            .data
+            .borrow()
+            .get(&addr)
+            .ok_or(Error::MissingData(addr))?
+            .clone();
+        Ok(QueryResponse { data })
+    }
+}
+
+#[test]
+fn dispatch_on_string() {
+    let contract: Contract<String> = Contract::default();
+
+    let mut deps = mock_dependencies();
+    let env = mock_env();
+    let info = mock_info("owner", &[]);
+
+    ExecMsg::Store {
+        addr: Addr::unchecked("addr1"),
+        data: "str1".to_owned(),
+    }
+    .dispatch(&contract, (deps.as_mut(), env.clone(), info.clone()))
+    .unwrap();
+
+    ExecMsg::Store {
+        addr: Addr::unchecked("addr2"),
+        data: "str2".to_owned(),
+    }
+    .dispatch(&contract, (deps.as_mut(), env.clone(), info))
+    .unwrap();
+
+    let resp = QueryMsg::Load {
+        addr: Addr::unchecked("addr2"),
+    }
+    .dispatch(&contract, (deps.as_ref(), env.clone()))
+    .unwrap();
+    let resp: QueryResponse<String> = from_binary(&resp).unwrap();
+    assert_eq!(
+        resp,
+        QueryResponse {
+            data: "str2".to_owned()
+        }
+    );
+
+    let err = QueryMsg::Load {
+        addr: Addr::unchecked("addr3"),
+    }
+    .dispatch(&contract, (deps.as_ref(), env))
+    .unwrap_err();
+    assert_eq!(err, Error::MissingData(Addr::unchecked("addr3")),);
+}
+
+#[test]
+fn dispatch_on_uint128() {
+    let contract: Contract<Uint128> = Contract::default();
+
+    let mut deps = mock_dependencies();
+    let env = mock_env();
+    let info = mock_info("owner", &[]);
+
+    ExecMsg::Store {
+        addr: Addr::unchecked("addr1"),
+        data: 100u128.into(),
+    }
+    .dispatch(&contract, (deps.as_mut(), env.clone(), info.clone()))
+    .unwrap();
+
+    ExecMsg::Store {
+        addr: Addr::unchecked("addr2"),
+        data: 200u128.into(),
+    }
+    .dispatch(&contract, (deps.as_mut(), env.clone(), info))
+    .unwrap();
+
+    let resp = QueryMsg::Load {
+        addr: Addr::unchecked("addr2"),
+    }
+    .dispatch(&contract, (deps.as_ref(), env.clone()))
+    .unwrap();
+    let resp: QueryResponse<Uint128> = from_binary(&resp).unwrap();
+    assert_eq!(
+        resp,
+        QueryResponse {
+            data: 200u128.into()
+        }
+    );
+
+    let err = QueryMsg::Load {
+        addr: Addr::unchecked("addr3"),
+    }
+    .dispatch(&contract, (deps.as_ref(), env))
+    .unwrap_err();
+    assert_eq!(err, Error::MissingData(Addr::unchecked("addr3")),);
+}
diff --git a/sylvia-derive/tests/messages_generation.rs b/sylvia-derive/tests/messages_generation.rs
new file mode 100644
index 0000000..ae3699a
--- /dev/null
+++ b/sylvia-derive/tests/messages_generation.rs
@@ -0,0 +1,54 @@
+use cosmwasm_std::{Addr, Decimal, Deps, DepsMut, Env, MessageInfo, Response, StdError};
+
+use sylvia_derive::interface;
+
+#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, schemars::JsonSchema)]
+pub struct QueryResult;
+
+#[interface]
+pub trait Interface {
+    type Error: From<StdError>;
+
+    #[msg(exec)]
+    fn no_args_execution(&self, ctx: (DepsMut, Env, MessageInfo)) -> Result<Response, Self::Error>;
+
+    #[msg(exec)]
+    fn argumented_execution(
+        &self,
+        ctx: (DepsMut, Env, MessageInfo),
+        addr: Addr,
+        coef: Decimal,
+        desc: String,
+    ) -> Result<Response, Self::Error>;
+
+    #[msg(query)]
+    fn no_args_query(&self, ctx: (Deps, Env)) -> Result<QueryResult, Self::Error>;
+
+    #[msg(query)]
+    fn argumented_query(&self, ctx: (Deps, Env), user: Addr) -> Result<QueryResult, Self::Error>;
+}
+
+#[test]
+fn messages_constructible() {
+    let no_args_exec = ExecMsg::NoArgsExecution {};
+    let _argumented_exec = ExecMsg::ArgumentedExecution {
+        addr: Addr::unchecked("owner"),
+        coef: Decimal::percent(10),
+        desc: "Some description".to_owned(),
+    };
+    let no_args_query = QueryMsg::NoArgsQuery {};
+    let _argumented_query = QueryMsg::ArgumentedQuery {
+        user: Addr::unchecked("owner"),
+    };
+
+    // Ensure no extra variants are generated
+    match no_args_exec {
+        ExecMsg::NoArgsExecution {} => (),
+        ExecMsg::ArgumentedExecution { .. } => (),
+    }
+
+    match no_args_query {
+        QueryMsg::NoArgsQuery {} => (),
+        QueryMsg::ArgumentedQuery { .. } => (),
+    }
+}
diff --git a/sylvia-derive/tests/separate_module.rs b/sylvia-derive/tests/separate_module.rs
new file mode 100644
index 0000000..5fc74ff
--- /dev/null
+++ b/sylvia-derive/tests/separate_module.rs
@@ -0,0 +1,43 @@
+use cosmwasm_std::{Addr, Decimal, Deps, DepsMut, Env, MessageInfo, Response, StdError};
+
+use sylvia_derive::interface;
+
+#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, schemars::JsonSchema)]
+pub struct QueryResult;
+
+#[interface(module=msg)]
+pub trait Interface {
+    type Error: From<StdError>;
+
+    #[msg(exec)]
+    fn no_args_execution(&self, ctx: (DepsMut, Env, MessageInfo)) -> Result<Response, Self::Error>;
+
+    #[msg(exec)]
+    fn argumented_execution(
+        &self,
+        ctx: (DepsMut, Env, MessageInfo),
+        addr: Addr,
+        coef: Decimal,
+        desc: String,
+    ) -> Result<Response, Self::Error>;
+
+    #[msg(query)]
+    fn no_args_query(&self, ctx: (Deps, Env)) -> Result<QueryResult, Self::Error>;
+
+    #[msg(query)]
+    fn argumented_query(&self, ctx: (Deps, Env), user: Addr) -> Result<QueryResult, Self::Error>;
+}
+
+#[test]
+fn messages_constructible() {
+    let _no_args_exec = msg::ExecMsg::NoArgsExecution {};
+    let _argumented_exec = msg::ExecMsg::ArgumentedExecution {
+        addr: Addr::unchecked("owner"),
+        coef: Decimal::percent(10),
+        desc: "Some description".to_owned(),
+    };
+    let _no_args_query = msg::QueryMsg::NoArgsQuery {};
+    let _argumented_query = msg::QueryMsg::ArgumentedQuery {
+        user: Addr::unchecked("owner"),
+    };
+}