Repository: CosmWasm/sylvia
Pull request #452 (2024-11-05 08:44:44)

feat: Better reply support (#418)

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/CosmWasm/sylvia/pull/452?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm) Report
Attention: Patch coverage is `81.14856%` with `151 lines` in your changes missing coverage. Please review.
> Project coverage is 72.19%. Comparing base [(`10e8eb9`)](https://app.codecov.io/gh/CosmWasm/sylvia/commit/10e8eb9ce722c4e2f2bed6bce950acbcefd2b937?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm) to head [(`c6450bd`)](https://app.codecov.io/gh/CosmWasm/sylvia/commit/c6450bd18128f0e663c7fb93ecf2c4c740b5f543?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm).

| [Files with missing lines](https://app.codecov.io/gh/CosmWasm/sylvia/pull/452?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm) | Patch % | Lines |
|---|---|---|
| [sylvia-derive/src/contract/communication/reply.rs](https://app.codecov.io/gh/CosmWasm/sylvia/pull/452?src=pr&el=tree&filepath=sylvia-derive%2Fsrc%2Fcontract%2Fcommunication%2Freply.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm#diff-c3lsdmlhLWRlcml2ZS9zcmMvY29udHJhY3QvY29tbXVuaWNhdGlvbi9yZXBseS5ycw==) | 89.52% | [35 Missing :warning: ](https://app.codecov.io/gh/CosmWasm/sylvia/pull/452?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm) |
| [sylvia/tests/reply\_generation.rs](https://app.codecov.io/gh/CosmWasm/sylvia/pull/452?src=pr&el=tree&filepath=sylvia%2Ftests%2Freply_generation.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm#diff-c3lsdmlhL3Rlc3RzL3JlcGx5X2dlbmVyYXRpb24ucnM=) | 0.00% | [14 Missing :warning: ](https://app.codecov.io/gh/CosmWasm/sylvia/pull/452?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm) |
| [sylvia/src/builder/instantiate.rs](https://app.codecov.io/gh/CosmWasm/sylvia/pull/452?src=pr&el=tree&filepath=sylvia%2Fsrc%2Fbuilder%2Finstantiate.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm#diff-c3lsdmlhL3NyYy9idWlsZGVyL2luc3RhbnRpYXRlLnJz) | 47.82% | [12 Missing :warning: ](https://app.codecov.io/gh/CosmWasm/sylvia/pull/452?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm) |
| [sylvia-derive/src/parser/attributes/msg.rs](https://app.codecov.io/gh/CosmWasm/sylvia/pull/452?src=pr&el=tree&filepath=sylvia-derive%2Fsrc%2Fparser%2Fattributes%2Fmsg.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm#diff-c3lsdmlhLWRlcml2ZS9zcmMvcGFyc2VyL2F0dHJpYnV0ZXMvbXNnLnJz) | 78.43% | [11 Missing :warning: ](https://app.codecov.io/gh/CosmWasm/sylvia/pull/452?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm) |
| [sylvia/src/ctx.rs](https://app.codecov.io/gh/CosmWasm/sylvia/pull/452?src=pr&el=tree&filepath=sylvia%2Fsrc%2Fctx.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm#diff-c3lsdmlhL3NyYy9jdHgucnM=) | 35.29% | [11 Missing :warning: ](https://app.codecov.io/gh/CosmWasm/sylvia/pull/452?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm) |
| [sylvia-derive/src/parser/attributes/data.rs](https://app.codecov.io/gh/CosmWasm/sylvia/pull/452?src=pr&el=tree&filepath=sylvia-derive%2Fsrc%2Fparser%2Fattributes%2Fdata.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm#diff-c3lsdmlhLWRlcml2ZS9zcmMvcGFyc2VyL2F0dHJpYnV0ZXMvZGF0YS5ycw==) | 62.96% | [10 Missing :warning: ](https://app.codecov.io/gh/CosmWasm/sylvia/pull/452?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm) |
| [sylvia-derive/src/entry\_points.rs](https://app.codecov.io/gh/CosmWasm/sylvia/pull/452?src=pr&el=tree&filepath=sylvia-derive%2Fsrc%2Fentry_points.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm#diff-c3lsdmlhLWRlcml2ZS9zcmMvZW50cnlfcG9pbnRzLnJz) | 0.00% | [8 Missing :warning: ](https://app.codecov.io/gh/CosmWasm/sylvia/pull/452?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm) |
| [sylvia-derive/src/parser/attributes/payload.rs](https://app.codecov.io/gh/CosmWasm/sylvia/pull/452?src=pr&el=tree&filepath=sylvia-derive%2Fsrc%2Fparser%2Fattributes%2Fpayload.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm#diff-c3lsdmlhLWRlcml2ZS9zcmMvcGFyc2VyL2F0dHJpYnV0ZXMvcGF5bG9hZC5ycw==) | 50.00% | [8 Missing :warning: ](https://app.codecov.io/gh/CosmWasm/sylvia/pull/452?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm) |
| [sylvia/tests/remote.rs](https://app.codecov.io/gh/CosmWasm/sylvia/pull/452?src=pr&el=tree&filepath=sylvia%2Ftests%2Fremote.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm#diff-c3lsdmlhL3Rlc3RzL3JlbW90ZS5ycw==) | 63.63% | [8 Missing :warning: ](https://app.codecov.io/gh/CosmWasm/sylvia/pull/452?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm) |
| [sylvia/tests/reply\_dispatch.rs](https://app.codecov.io/gh/CosmWasm/sylvia/pull/452?src=pr&el=tree&filepath=sylvia%2Ftests%2Freply_dispatch.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm#diff-c3lsdmlhL3Rlc3RzL3JlcGx5X2Rpc3BhdGNoLnJz) | 89.61% | [8 Missing :warning: ](https://app.codecov.io/gh/CosmWasm/sylvia/pull/452?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm) |
| ... and [9 more](https://app.codecov.io/gh/CosmWasm/sylvia/pull/452?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm) | |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main     #452      +/-   ##
==========================================
+ Coverage   70.08%   72.19%   +2.11%     
==========================================
  Files          53       63      +10     
  Lines        3085     3812     +727     
==========================================
+ Hits         2162     2752     +590     
- Misses        923     1060     +137     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/CosmWasm/sylvia/pull/452?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm).

kulikthebird: Just one more question. What would happen if we implement such a reply:
```rust
#[sv::msg(reply, reply_on=success)]
    fn some_reply(
        &self,
        _ctx: ReplyCtx,
        #[sv::data] _data: ComplexData,
        #[sv::payload(raw)] _payload: Binary,
        some_custom_arg: String    // We provide sv::payload(raw) and a custom arg
    ) -> Result<Response, ContractError> {
        Ok(Response::new())
    }
```
jawoznia: > Just one more question. What would happen if we implement such a reply:
> 
> ```rust
> #[sv::msg(reply, reply_on=success)]
>     fn some_reply(
>         &self,
>         _ctx: ReplyCtx,
>         #[sv::data] _data: ComplexData,
>         #[sv::payload(raw)] _payload: Binary,
>         some_custom_arg: String    // We provide sv::payload(raw) and a custom arg
>     ) -> Result<Response, ContractError> {
>         Ok(Response::new())
>     }
> ```

Good question. I haven't thought about that. I will check

Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 5ddf242..6bb7761 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -22,9 +22,9 @@ checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
 
 [[package]]
 name = "anyhow"
-version = "1.0.86"
+version = "1.0.93"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
+checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775"
 
 [[package]]
 name = "ark-bls12-381"
@@ -212,9 +212,9 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
 
 [[package]]
 name = "const_panic"
-version = "0.2.8"
+version = "0.2.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6051f239ecec86fde3410901ab7860d458d160371533842974fc61f96d15879b"
+checksum = "7782af8f90fe69a4bb41e460abe1727d493403d8b2cc43201a3a3e906b24379f"
 
 [[package]]
 name = "convert_case"
@@ -227,15 +227,15 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-core"
-version = "2.1.3"
+version = "2.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d905990ef3afb5753bb709dc7de88e9e370aa32bcc2f31731d4b533b63e82490"
+checksum = "5f6ceb8624260d0d3a67c4e1a1d43fc7e9406720afbcb124521501dd138f90aa"
 
 [[package]]
 name = "cosmwasm-crypto"
-version = "2.1.3"
+version = "2.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b2a7bd9c1dd9a377a4dc0f4ad97d24b03c33798cd5a6d7ceb8869b41c5d2f2d"
+checksum = "4125381e5fd7fefe9f614640049648088015eca2b60d861465329a5d87dfa538"
 dependencies = [
  "ark-bls12-381",
  "ark-ec",
@@ -251,49 +251,49 @@ dependencies = [
  "rand_core",
  "rayon",
  "sha2",
- "thiserror",
+ "thiserror 1.0.64",
 ]
 
 [[package]]
 name = "cosmwasm-derive"
-version = "2.1.3"
+version = "2.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "029910b409398fdf81955d7301b906caf81f2c42b013ea074fbd89720229c424"
+checksum = "1b5658b1dc64e10b56ae7a449f678f96932a96f6cfad1769d608d1d1d656480a"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.75",
+ "syn 2.0.87",
 ]
 
 [[package]]
 name = "cosmwasm-schema"
-version = "2.1.3"
+version = "2.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4bc0d4d85e83438ab9a0fea9348446f7268bc016aacfebce37e998559f151294"
+checksum = "f86b4d949b6041519c58993a73f4bbfba8083ba14f7001eae704865a09065845"
 dependencies = [
  "cosmwasm-schema-derive",
  "schemars",
  "serde",
  "serde_json",
- "thiserror",
+ "thiserror 1.0.64",
 ]
 
 [[package]]
 name = "cosmwasm-schema-derive"
-version = "2.1.3"
+version = "2.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "edf5c8adac41bb7751c050d7c4c18675be19ee128714454454575e894424eeef"
+checksum = "c8ef1b5835a65fcca3ab8b9a02b4f4dacc78e233a5c2f20b270efb9db0666d12"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.75",
+ "syn 2.0.87",
 ]
 
 [[package]]
 name = "cosmwasm-std"
-version = "2.1.3"
+version = "2.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "51dec99a2e478715c0a4277f0dbeadbb8466500eb7dec873d0924edd086e77f1"
+checksum = "70eb7ab0c1e99dd6207496963ba2a457c4128ac9ad9c72a83f8d9808542b849b"
 dependencies = [
  "base64",
  "bech32",
@@ -309,14 +309,14 @@ dependencies = [
  "serde-json-wasm",
  "sha2",
  "static_assertions",
- "thiserror",
+ "thiserror 1.0.64",
 ]
 
 [[package]]
 name = "cpufeatures"
-version = "0.2.13"
+version = "0.2.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad"
+checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0"
 dependencies = [
  "libc",
 ]
@@ -392,27 +392,27 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.75",
+ "syn 2.0.87",
 ]
 
 [[package]]
 name = "cw-multi-test"
-version = "2.1.1"
+version = "2.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b0ae276e7a06ad1b7e7da78a3d68aba80634cde30ee7fe8259a94e653603fef8"
+checksum = "1149fe104344cc4f4ca0fc784b7411042fd1626813fe85fd412b05252a0ae9d8"
 dependencies = [
  "anyhow",
  "bech32",
+ "cosmwasm-schema",
  "cosmwasm-std",
  "cw-storage-plus",
  "cw-utils",
- "derivative",
  "itertools 0.13.0",
  "prost",
  "schemars",
  "serde",
  "sha2",
- "thiserror",
+ "thiserror 2.0.0",
 ]
 
 [[package]]
@@ -436,7 +436,7 @@ dependencies = [
  "cosmwasm-std",
  "schemars",
  "serde",
- "thiserror",
+ "thiserror 1.0.64",
 ]
 
 [[package]]
@@ -477,7 +477,7 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.75",
+ "syn 2.0.87",
  "unicode-xid",
 ]
 
@@ -657,9 +657,9 @@ dependencies = [
 
 [[package]]
 name = "indexmap"
-version = "2.4.0"
+version = "2.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c"
+checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5"
 dependencies = [
  "equivalent",
  "hashbrown 0.14.5",
@@ -812,11 +812,11 @@ dependencies = [
 
 [[package]]
 name = "proc-macro-crate"
-version = "3.1.0"
+version = "3.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284"
+checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b"
 dependencies = [
- "toml_edit 0.21.1",
+ "toml_edit",
 ]
 
 [[package]]
@@ -845,18 +845,18 @@ dependencies = [
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.86"
+version = "1.0.89"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
+checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
 dependencies = [
  "unicode-ident",
 ]
 
 [[package]]
 name = "prost"
-version = "0.13.1"
+version = "0.13.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e13db3d3fde688c61e2446b4d843bc27a7e8af269a69440c0308021dc92333cc"
+checksum = "7b0487d90e047de87f984913713b85c601c05609aad5b0df4b4573fbf69aa13f"
 dependencies = [
  "bytes",
  "prost-derive",
@@ -864,22 +864,22 @@ dependencies = [
 
 [[package]]
 name = "prost-derive"
-version = "0.13.1"
+version = "0.13.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "18bec9b0adc4eba778b33684b7ba3e7137789434769ee3ce3930463ef904cfca"
+checksum = "e9552f850d5f0964a4e4d0bf306459ac29323ddfbae05e35a7c0d35cb0803cc5"
 dependencies = [
  "anyhow",
  "itertools 0.13.0",
  "proc-macro2",
  "quote",
- "syn 2.0.75",
+ "syn 2.0.87",
 ]
 
 [[package]]
 name = "quote"
-version = "1.0.36"
+version = "1.0.37"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
+checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
 dependencies = [
  "proc-macro2",
 ]
@@ -945,9 +945,9 @@ dependencies = [
 
 [[package]]
 name = "rustc_version"
-version = "0.4.0"
+version = "0.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
+checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
 dependencies = [
  "semver",
 ]
@@ -979,7 +979,7 @@ dependencies = [
  "proc-macro2",
  "quote",
  "serde_derive_internals",
- "syn 2.0.75",
+ "syn 2.0.87",
 ]
 
 [[package]]
@@ -1003,9 +1003,9 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
 
 [[package]]
 name = "serde"
-version = "1.0.208"
+version = "1.0.214"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2"
+checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5"
 dependencies = [
  "serde_derive",
 ]
@@ -1030,13 +1030,13 @@ dependencies = [
 
 [[package]]
 name = "serde_derive"
-version = "1.0.208"
+version = "1.0.214"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf"
+checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.75",
+ "syn 2.0.87",
 ]
 
 [[package]]
@@ -1047,14 +1047,14 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.75",
+ "syn 2.0.87",
 ]
 
 [[package]]
 name = "serde_json"
-version = "1.0.125"
+version = "1.0.128"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed"
+checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8"
 dependencies = [
  "itoa",
  "memchr",
@@ -1115,13 +1115,14 @@ dependencies = [
  "cw-storage-plus",
  "cw-utils",
  "derivative",
+ "itertools 0.13.0",
  "konst",
  "schemars",
  "serde",
  "serde-cw-value",
  "serde-json-wasm",
  "sylvia-derive",
- "thiserror",
+ "thiserror 2.0.0",
  "trybuild",
 ]
 
@@ -1143,8 +1144,8 @@ dependencies = [
  "serde",
  "sylvia",
  "sylvia-runtime-macros",
- "syn 2.0.75",
- "thiserror",
+ "syn 2.0.87",
+ "thiserror 2.0.0",
 ]
 
 [[package]]
@@ -1171,9 +1172,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "2.0.75"
+version = "2.0.87"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9"
+checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1191,22 +1192,42 @@ dependencies = [
 
 [[package]]
 name = "thiserror"
-version = "1.0.63"
+version = "1.0.64"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84"
+dependencies = [
+ "thiserror-impl 1.0.64",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
+checksum = "15291287e9bff1bc6f9ff3409ed9af665bec7a5fc8ac079ea96be07bca0e2668"
 dependencies = [
- "thiserror-impl",
+ "thiserror-impl 2.0.0",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "1.0.63"
+version = "1.0.64"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
+checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.75",
+ "syn 2.0.87",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22efd00f33f93fa62848a7cab956c3d38c8d43095efda1decfc2b3a5dc0b8972"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.87",
 ]
 
 [[package]]
@@ -1218,7 +1239,7 @@ dependencies = [
  "serde",
  "serde_spanned",
  "toml_datetime",
- "toml_edit 0.22.20",
+ "toml_edit",
 ]
 
 [[package]]
@@ -1230,17 +1251,6 @@ dependencies = [
  "serde",
 ]
 
-[[package]]
-name = "toml_edit"
-version = "0.21.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1"
-dependencies = [
- "indexmap",
- "toml_datetime",
- "winnow 0.5.40",
-]
-
 [[package]]
 name = "toml_edit"
 version = "0.22.20"
@@ -1251,7 +1261,7 @@ dependencies = [
  "serde",
  "serde_spanned",
  "toml_datetime",
- "winnow 0.6.18",
+ "winnow",
 ]
 
 [[package]]
@@ -1401,15 +1411,6 @@ version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
 
-[[package]]
-name = "winnow"
-version = "0.5.40"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876"
-dependencies = [
- "memchr",
-]
-
 [[package]]
 name = "winnow"
 version = "0.6.18"
@@ -1437,7 +1438,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.75",
+ "syn 2.0.87",
 ]
 
 [[package]]
@@ -1457,5 +1458,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.75",
+ "syn 2.0.87",
 ]
diff --git a/Cargo.toml b/Cargo.toml
index 0b76ff1..e28adf9 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -8,15 +8,15 @@ version = "1.2.1"
 
 [workspace.dependencies]
 sylvia-derive = { version = "1.2.1", path = "sylvia-derive" }
-anyhow = "1.0.86"
-cosmwasm-schema = "2.1.1"
-cosmwasm-std = "2.1.1"
-cw-multi-test = "2.1.0"
+anyhow = "1.0.93"
+cosmwasm-schema = "2.1.4"
+cosmwasm-std = "2.1.4"
+cw-multi-test = "2.2.0"
 cw-storage-plus = "2.0.0"
 schemars = "0.8.21"
 cw-utils = "2.0.0"
-serde = { version = "1.0.204", default-features = false, features = ["derive"] }
-thiserror = "1.0.63"
+serde = { version = "1.0.214", default-features = false, features = ["derive"] }
+thiserror = "2.0.0"
 
 [workspace.metadata.docs.rs]
 all-features = true
diff --git a/examples/Cargo.lock b/examples/Cargo.lock
index d9a8250..0bd7515 100644
--- a/examples/Cargo.lock
+++ b/examples/Cargo.lock
@@ -22,9 +22,9 @@ checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
 
 [[package]]
 name = "anyhow"
-version = "1.0.86"
+version = "1.0.93"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
+checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775"
 
 [[package]]
 name = "ark-bls12-381"
@@ -233,15 +233,15 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-core"
-version = "2.1.3"
+version = "2.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d905990ef3afb5753bb709dc7de88e9e370aa32bcc2f31731d4b533b63e82490"
+checksum = "5f6ceb8624260d0d3a67c4e1a1d43fc7e9406720afbcb124521501dd138f90aa"
 
 [[package]]
 name = "cosmwasm-crypto"
-version = "2.1.3"
+version = "2.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b2a7bd9c1dd9a377a4dc0f4ad97d24b03c33798cd5a6d7ceb8869b41c5d2f2d"
+checksum = "4125381e5fd7fefe9f614640049648088015eca2b60d861465329a5d87dfa538"
 dependencies = [
  "ark-bls12-381",
  "ark-ec",
@@ -257,49 +257,49 @@ dependencies = [
  "rand_core",
  "rayon",
  "sha2",
- "thiserror",
+ "thiserror 1.0.63",
 ]
 
 [[package]]
 name = "cosmwasm-derive"
-version = "2.1.3"
+version = "2.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "029910b409398fdf81955d7301b906caf81f2c42b013ea074fbd89720229c424"
+checksum = "1b5658b1dc64e10b56ae7a449f678f96932a96f6cfad1769d608d1d1d656480a"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.72",
+ "syn 2.0.87",
 ]
 
 [[package]]
 name = "cosmwasm-schema"
-version = "2.1.3"
+version = "2.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4bc0d4d85e83438ab9a0fea9348446f7268bc016aacfebce37e998559f151294"
+checksum = "f86b4d949b6041519c58993a73f4bbfba8083ba14f7001eae704865a09065845"
 dependencies = [
  "cosmwasm-schema-derive",
  "schemars",
  "serde",
  "serde_json",
- "thiserror",
+ "thiserror 1.0.63",
 ]
 
 [[package]]
 name = "cosmwasm-schema-derive"
-version = "2.1.3"
+version = "2.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "edf5c8adac41bb7751c050d7c4c18675be19ee128714454454575e894424eeef"
+checksum = "c8ef1b5835a65fcca3ab8b9a02b4f4dacc78e233a5c2f20b270efb9db0666d12"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.72",
+ "syn 2.0.87",
 ]
 
 [[package]]
 name = "cosmwasm-std"
-version = "2.1.3"
+version = "2.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "51dec99a2e478715c0a4277f0dbeadbb8466500eb7dec873d0924edd086e77f1"
+checksum = "70eb7ab0c1e99dd6207496963ba2a457c4128ac9ad9c72a83f8d9808542b849b"
 dependencies = [
  "base64",
  "bech32",
@@ -315,7 +315,7 @@ dependencies = [
  "serde-json-wasm",
  "sha2",
  "static_assertions",
- "thiserror",
+ "thiserror 1.0.63",
 ]
 
 [[package]]
@@ -398,7 +398,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.72",
+ "syn 2.0.87",
 ]
 
 [[package]]
@@ -419,22 +419,22 @@ dependencies = [
 
 [[package]]
 name = "cw-multi-test"
-version = "2.1.1"
+version = "2.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b0ae276e7a06ad1b7e7da78a3d68aba80634cde30ee7fe8259a94e653603fef8"
+checksum = "1149fe104344cc4f4ca0fc784b7411042fd1626813fe85fd412b05252a0ae9d8"
 dependencies = [
  "anyhow",
  "bech32",
+ "cosmwasm-schema",
  "cosmwasm-std",
  "cw-storage-plus",
  "cw-utils",
- "derivative",
  "itertools 0.13.0",
  "prost",
  "schemars",
  "serde",
  "sha2",
- "thiserror",
+ "thiserror 2.0.0",
 ]
 
 [[package]]
@@ -458,7 +458,7 @@ dependencies = [
  "cosmwasm-std",
  "schemars",
  "serde",
- "thiserror",
+ "thiserror 1.0.63",
 ]
 
 [[package]]
@@ -478,7 +478,7 @@ dependencies = [
  "cw1-whitelist",
  "cw2",
  "sylvia",
- "thiserror",
+ "thiserror 2.0.0",
  "whitelist",
 ]
 
@@ -491,7 +491,7 @@ dependencies = [
  "cw1",
  "cw2",
  "sylvia",
- "thiserror",
+ "thiserror 2.0.0",
  "whitelist",
 ]
 
@@ -507,7 +507,7 @@ dependencies = [
  "schemars",
  "semver",
  "serde",
- "thiserror",
+ "thiserror 1.0.63",
 ]
 
 [[package]]
@@ -531,7 +531,7 @@ dependencies = [
  "cw20-minting",
  "semver",
  "sylvia",
- "thiserror",
+ "thiserror 2.0.0",
 ]
 
 [[package]]
@@ -593,7 +593,7 @@ checksum = "2bba3e9872d7c58ce7ef0fcf1844fcc3e23ef2a58377b50df35dd98e42a5726e"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.72",
+ "syn 2.0.87",
  "unicode-xid",
 ]
 
@@ -759,7 +759,7 @@ dependencies = [
  "cw1",
  "generic",
  "sylvia",
- "thiserror",
+ "thiserror 2.0.0",
 ]
 
 [[package]]
@@ -803,6 +803,12 @@ dependencies = [
  "allocator-api2",
 ]
 
+[[package]]
+name = "hashbrown"
+version = "0.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3"
+
 [[package]]
 name = "hex"
 version = "0.4.3"
@@ -820,12 +826,12 @@ dependencies = [
 
 [[package]]
 name = "indexmap"
-version = "2.2.5"
+version = "2.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4"
+checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
 dependencies = [
  "equivalent",
- "hashbrown 0.14.3",
+ "hashbrown 0.15.1",
 ]
 
 [[package]]
@@ -975,9 +981,9 @@ dependencies = [
 
 [[package]]
 name = "proc-macro-crate"
-version = "3.1.0"
+version = "3.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284"
+checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b"
 dependencies = [
  "toml_edit",
 ]
@@ -1008,18 +1014,18 @@ dependencies = [
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.86"
+version = "1.0.89"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
+checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
 dependencies = [
  "unicode-ident",
 ]
 
 [[package]]
 name = "prost"
-version = "0.13.1"
+version = "0.13.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e13db3d3fde688c61e2446b4d843bc27a7e8af269a69440c0308021dc92333cc"
+checksum = "7b0487d90e047de87f984913713b85c601c05609aad5b0df4b4573fbf69aa13f"
 dependencies = [
  "bytes",
  "prost-derive",
@@ -1027,22 +1033,22 @@ dependencies = [
 
 [[package]]
 name = "prost-derive"
-version = "0.13.1"
+version = "0.13.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "18bec9b0adc4eba778b33684b7ba3e7137789434769ee3ce3930463ef904cfca"
+checksum = "e9552f850d5f0964a4e4d0bf306459ac29323ddfbae05e35a7c0d35cb0803cc5"
 dependencies = [
  "anyhow",
  "itertools 0.13.0",
  "proc-macro2",
  "quote",
- "syn 2.0.72",
+ "syn 2.0.87",
 ]
 
 [[package]]
 name = "quote"
-version = "1.0.36"
+version = "1.0.37"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
+checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
 dependencies = [
  "proc-macro2",
 ]
@@ -1142,7 +1148,7 @@ dependencies = [
  "proc-macro2",
  "quote",
  "serde_derive_internals",
- "syn 2.0.72",
+ "syn 2.0.87",
 ]
 
 [[package]]
@@ -1166,9 +1172,9 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
 
 [[package]]
 name = "serde"
-version = "1.0.208"
+version = "1.0.214"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2"
+checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5"
 dependencies = [
  "serde_derive",
 ]
@@ -1193,13 +1199,13 @@ dependencies = [
 
 [[package]]
 name = "serde_derive"
-version = "1.0.208"
+version = "1.0.214"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf"
+checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.72",
+ "syn 2.0.87",
 ]
 
 [[package]]
@@ -1210,7 +1216,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.72",
+ "syn 2.0.87",
 ]
 
 [[package]]
@@ -1284,7 +1290,7 @@ dependencies = [
  "proc-macro-error",
  "proc-macro2",
  "quote",
- "syn 2.0.72",
+ "syn 2.0.87",
 ]
 
 [[package]]
@@ -1300,9 +1306,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "2.0.72"
+version = "2.0.87"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af"
+checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1315,7 +1321,16 @@ version = "1.0.63"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
 dependencies = [
- "thiserror-impl",
+ "thiserror-impl 1.0.63",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15291287e9bff1bc6f9ff3409ed9af665bec7a5fc8ac079ea96be07bca0e2668"
+dependencies = [
+ "thiserror-impl 2.0.0",
 ]
 
 [[package]]
@@ -1326,20 +1341,31 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.72",
+ "syn 2.0.87",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22efd00f33f93fa62848a7cab956c3d38c8d43095efda1decfc2b3a5dc0b8972"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.87",
 ]
 
 [[package]]
 name = "toml_datetime"
-version = "0.6.5"
+version = "0.6.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
+checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
 
 [[package]]
 name = "toml_edit"
-version = "0.21.1"
+version = "0.22.22"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1"
+checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
 dependencies = [
  "indexmap",
  "toml_datetime",
@@ -1406,9 +1432,9 @@ dependencies = [
 
 [[package]]
 name = "winnow"
-version = "0.5.40"
+version = "0.6.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876"
+checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b"
 dependencies = [
  "memchr",
 ]
@@ -1440,7 +1466,7 @@ checksum = "125139de3f6b9d625c39e2efdd73d41bdac468ccd556556440e322be0e1bbd91"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.72",
+ "syn 2.0.87",
 ]
 
 [[package]]
@@ -1451,7 +1477,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.72",
+ "syn 2.0.87",
 ]
 
 [[package]]
@@ -1471,5 +1497,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.72",
+ "syn 2.0.87",
 ]
diff --git a/examples/Cargo.toml b/examples/Cargo.toml
index f99702b..8a8ef01 100644
--- a/examples/Cargo.toml
+++ b/examples/Cargo.toml
@@ -27,9 +27,10 @@ version = "0.5.0"
 edition = "2021"
 
 [workspace.dependencies]
+sylvia = { path = "../sylvia" }
 cw-storage-plus = "2.0.0"
 cw-utils = "2.0.0"
 cw2 = "2.0.0"
 semver = "1.0.23"
-thiserror = "1.0.63"
+thiserror = "2.0.0"
 assert_matches = "1.5.0"
diff --git a/examples/contracts/custom/Cargo.toml b/examples/contracts/custom/Cargo.toml
index 9c0b744..be8e71d 100644
--- a/examples/contracts/custom/Cargo.toml
+++ b/examples/contracts/custom/Cargo.toml
@@ -18,7 +18,7 @@ mt = ["library", "sylvia/mt"]
 [dependencies]
 cw1 = { path = "../../interfaces/cw1" }
 cw-storage-plus = { workspace = true }
-sylvia = { path = "../../../sylvia" }
+sylvia = { workspace = true }
 
 [dev-dependencies]
-sylvia = { path = "../../../sylvia", features = ["mt"] }
+sylvia = { workspace = true, features = ["mt"] }
diff --git a/examples/contracts/custom/src/contract.rs b/examples/contracts/custom/src/contract.rs
index b5bbc66..1c070b3 100644
--- a/examples/contracts/custom/src/contract.rs
+++ b/examples/contracts/custom/src/contract.rs
@@ -1,7 +1,7 @@
 use cw_storage_plus::Item;
 use sylvia::contract;
+use sylvia::ctx::{ExecCtx, InstantiateCtx, QueryCtx, SudoCtx};
 use sylvia::cw_std::{CosmosMsg, QueryRequest, Response, StdResult};
-use sylvia::types::{ExecCtx, InstantiateCtx, QueryCtx, SudoCtx};
 
 #[cfg(not(feature = "library"))]
 use sylvia::entry_points;
diff --git a/examples/contracts/custom/src/cw1.rs b/examples/contracts/custom/src/cw1.rs
index a2817a5..7ac9702 100644
--- a/examples/contracts/custom/src/cw1.rs
+++ b/examples/contracts/custom/src/cw1.rs
@@ -1,6 +1,6 @@
 use cw1::{CanExecuteResp, Cw1};
+use sylvia::ctx::{ExecCtx, QueryCtx};
 use sylvia::cw_std::{CosmosMsg, Empty, Response, StdError, StdResult};
-use sylvia::types::{ExecCtx, QueryCtx};
 
 use crate::contract::CustomContract;
 
diff --git a/examples/contracts/cw1-subkeys/Cargo.toml b/examples/contracts/cw1-subkeys/Cargo.toml
index bc9c0ff..3d3d1ed 100644
--- a/examples/contracts/cw1-subkeys/Cargo.toml
+++ b/examples/contracts/cw1-subkeys/Cargo.toml
@@ -17,10 +17,10 @@ cw1 = { path = "../../interfaces/cw1" }
 whitelist = { path = "../../interfaces/whitelist" }
 cw1-whitelist = { path = "../cw1-whitelist", features = ["library"] }
 cw2 = { workspace = true }
-sylvia = { path = "../../../sylvia" }
+sylvia = { workspace = true }
 thiserror = { workspace = true }
 
 [dev-dependencies]
-sylvia = { path = "../../../sylvia", features = ["mt"] }
+sylvia = { workspace = true, features = ["mt"] }
 cw1-whitelist = { path = "../cw1-whitelist", features = ["mt"] }
 cw1 = { path = "../../interfaces/cw1", features = ["mt"] }
diff --git a/examples/contracts/cw1-subkeys/src/contract.rs b/examples/contracts/cw1-subkeys/src/contract.rs
index 8b52559..2aa9fec 100644
--- a/examples/contracts/cw1-subkeys/src/contract.rs
+++ b/examples/contracts/cw1-subkeys/src/contract.rs
@@ -3,11 +3,12 @@ use cw2::set_contract_version;
 use cw_storage_plus::{Bound, Map};
 use cw_utils::Expiration;
 use sylvia::contract;
+use sylvia::ctx::{ExecCtx, InstantiateCtx, QueryCtx};
 use sylvia::cw_std::{
     ensure, ensure_ne, Addr, BankMsg, Coin, CosmosMsg, Deps, DistributionMsg, Env, Order, Response,
     StakingMsg, StdResult,
 };
-use sylvia::types::{CustomMsg, CustomQuery, ExecCtx, InstantiateCtx, QueryCtx};
+use sylvia::types::{CustomMsg, CustomQuery};
 
 #[cfg(not(feature = "library"))]
 use sylvia::cw_std::Empty;
diff --git a/examples/contracts/cw1-subkeys/src/cw1.rs b/examples/contracts/cw1-subkeys/src/cw1.rs
index 9b1879b..1649859 100644
--- a/examples/contracts/cw1-subkeys/src/cw1.rs
+++ b/examples/contracts/cw1-subkeys/src/cw1.rs
@@ -1,6 +1,7 @@
 use cw1::{CanExecuteResp, Cw1};
+use sylvia::ctx::{ExecCtx, QueryCtx};
 use sylvia::cw_std::{ensure, Addr, CosmosMsg, Response, StdResult};
-use sylvia::types::{CustomMsg, CustomQuery, ExecCtx, QueryCtx};
+use sylvia::types::{CustomMsg, CustomQuery};
 
 use crate::contract::Cw1SubkeysContract;
 use crate::error::ContractError;
diff --git a/examples/contracts/cw1-subkeys/src/whitelist.rs b/examples/contracts/cw1-subkeys/src/whitelist.rs
index bbd1fb0..85a1dbf 100644
--- a/examples/contracts/cw1-subkeys/src/whitelist.rs
+++ b/examples/contracts/cw1-subkeys/src/whitelist.rs
@@ -1,5 +1,6 @@
+use sylvia::ctx::{ExecCtx, QueryCtx};
 use sylvia::cw_std::{Response, StdResult};
-use sylvia::types::{CustomMsg, CustomQuery, ExecCtx, QueryCtx};
+use sylvia::types::{CustomMsg, CustomQuery};
 use whitelist::responses::AdminListResponse;
 use whitelist::Whitelist;
 
diff --git a/examples/contracts/cw1-whitelist/Cargo.toml b/examples/contracts/cw1-whitelist/Cargo.toml
index abf4f88..013f70b 100644
--- a/examples/contracts/cw1-whitelist/Cargo.toml
+++ b/examples/contracts/cw1-whitelist/Cargo.toml
@@ -16,7 +16,7 @@ library = []
 mt = ["sylvia/mt", "library"]
 
 [dependencies]
-sylvia = { path = "../../../sylvia" }
+sylvia = { workspace = true }
 cw1 = { path = "../../interfaces/cw1" }
 whitelist = { path = "../../interfaces/whitelist" }
 cw-storage-plus = { workspace = true }
@@ -25,4 +25,4 @@ cw2 = { workspace = true }
 
 [dev-dependencies]
 assert_matches = { workspace = true }
-sylvia = { path = "../../../sylvia", features = ["mt"] }
+sylvia = { workspace = true, features = ["mt"] }
diff --git a/examples/contracts/cw1-whitelist/src/contract.rs b/examples/contracts/cw1-whitelist/src/contract.rs
index 531b202..f86ad40 100644
--- a/examples/contracts/cw1-whitelist/src/contract.rs
+++ b/examples/contracts/cw1-whitelist/src/contract.rs
@@ -2,8 +2,9 @@ use crate::error::ContractError;
 use cw2::set_contract_version;
 use cw_storage_plus::{Item, Map};
 use sylvia::contract;
+use sylvia::ctx::InstantiateCtx;
 use sylvia::cw_std::{Addr, Deps, Empty, Response};
-use sylvia::types::{CustomMsg, CustomQuery, InstantiateCtx};
+use sylvia::types::{CustomMsg, CustomQuery};
 
 #[cfg(not(feature = "library"))]
 use sylvia::entry_points;
diff --git a/examples/contracts/cw1-whitelist/src/cw1.rs b/examples/contracts/cw1-whitelist/src/cw1.rs
index 8403060..614329b 100644
--- a/examples/contracts/cw1-whitelist/src/cw1.rs
+++ b/examples/contracts/cw1-whitelist/src/cw1.rs
@@ -1,6 +1,7 @@
 use cw1::{CanExecuteResp, Cw1};
+use sylvia::ctx::{ExecCtx, QueryCtx};
 use sylvia::cw_std::{Addr, CosmosMsg, Response, StdResult};
-use sylvia::types::{CustomMsg, CustomQuery, ExecCtx, QueryCtx};
+use sylvia::types::{CustomMsg, CustomQuery};
 
 use crate::contract::Cw1WhitelistContract;
 use crate::error::ContractError;
diff --git a/examples/contracts/cw1-whitelist/src/whitelist.rs b/examples/contracts/cw1-whitelist/src/whitelist.rs
index e292d1e..b296be0 100644
--- a/examples/contracts/cw1-whitelist/src/whitelist.rs
+++ b/examples/contracts/cw1-whitelist/src/whitelist.rs
@@ -1,5 +1,6 @@
+use sylvia::ctx::{ExecCtx, QueryCtx};
 use sylvia::cw_std::{Empty, Order, Response, StdResult};
-use sylvia::types::{CustomMsg, CustomQuery, ExecCtx, QueryCtx};
+use sylvia::types::{CustomMsg, CustomQuery};
 use whitelist::responses::AdminListResponse;
 use whitelist::Whitelist;
 
diff --git a/examples/contracts/cw20-base/Cargo.toml b/examples/contracts/cw20-base/Cargo.toml
index 7df2731..182d9e1 100644
--- a/examples/contracts/cw20-base/Cargo.toml
+++ b/examples/contracts/cw20-base/Cargo.toml
@@ -23,13 +23,13 @@ cw20-allowances = { path = "../../interfaces/cw20-allowances" }
 cw20-marketing = { path = "../../interfaces/cw20-marketing" }
 cw20-minting = { path = "../../interfaces/cw20-minting" }
 semver = { workspace = true }
-sylvia = { path = "../../../sylvia" }
+sylvia = { workspace = true }
 thiserror = { workspace = true }
 
 [dev-dependencies]
 assert_matches = { workspace = true }
 cw-utils = { workspace = true }
-sylvia = { path = "../../../sylvia", features = ["mt"] }
+sylvia = { workspace = true, features = ["mt"] }
 cw20-allowances = { path = "../../interfaces/cw20-allowances", features = [
     "mt",
 ] }
diff --git a/examples/contracts/cw20-base/src/allowances.rs b/examples/contracts/cw20-base/src/allowances.rs
index bd9af05..ced4f0e 100644
--- a/examples/contracts/cw20-base/src/allowances.rs
+++ b/examples/contracts/cw20-base/src/allowances.rs
@@ -5,8 +5,9 @@ use cw20_allowances::responses::{
 use cw20_allowances::Cw20Allowances;
 use cw_storage_plus::{Bound, Bounder};
 use cw_utils::Expiration;
+use sylvia::ctx::{ExecCtx, QueryCtx};
 use sylvia::cw_std::{Addr, Binary, Order, Response, StdError, StdResult, Uint128};
-use sylvia::types::{CustomMsg, CustomQuery, ExecCtx, QueryCtx};
+use sylvia::types::{CustomMsg, CustomQuery};
 
 use crate::contract::Cw20Base;
 use crate::error::ContractError;
diff --git a/examples/contracts/cw20-base/src/contract.rs b/examples/contracts/cw20-base/src/contract.rs
index 97d88a2..f712fb5 100644
--- a/examples/contracts/cw20-base/src/contract.rs
+++ b/examples/contracts/cw20-base/src/contract.rs
@@ -8,12 +8,13 @@ use cw20_marketing::Logo;
 use cw20_minting::responses::MinterResponse;
 use cw_storage_plus::{Item, Map};
 use sylvia::contract;
+use sylvia::ctx::{ExecCtx, InstantiateCtx, MigrateCtx, QueryCtx};
 use sylvia::cw_schema::cw_serde;
 use sylvia::cw_std::{
     ensure, Addr, Binary, BlockInfo, DepsMut, Order, Response, StdError, StdResult, Storage,
     Uint128,
 };
-use sylvia::types::{CustomMsg, CustomQuery, ExecCtx, InstantiateCtx, MigrateCtx, QueryCtx};
+use sylvia::types::{CustomMsg, CustomQuery};
 
 #[cfg(not(feature = "library"))]
 use sylvia::cw_std::Empty;
diff --git a/examples/contracts/cw20-base/src/marketing.rs b/examples/contracts/cw20-base/src/marketing.rs
index 4525935..0a92c2b 100644
--- a/examples/contracts/cw20-base/src/marketing.rs
+++ b/examples/contracts/cw20-base/src/marketing.rs
@@ -3,8 +3,9 @@ use crate::error::ContractError;
 use crate::validation::verify_logo;
 use cw20_marketing::responses::{DownloadLogoResponse, LogoInfo, MarketingInfoResponse};
 use cw20_marketing::{Cw20Marketing, EmbeddedLogo, Logo};
+use sylvia::ctx::{ExecCtx, QueryCtx};
 use sylvia::cw_std::{Response, StdError, StdResult};
-use sylvia::types::{CustomMsg, CustomQuery, ExecCtx, QueryCtx};
+use sylvia::types::{CustomMsg, CustomQuery};
 
 impl<E, Q> Cw20Marketing for Cw20Base<E, Q>
 where
diff --git a/examples/contracts/cw20-base/src/minting.rs b/examples/contracts/cw20-base/src/minting.rs
index d7d6a38..d3316fe 100644
--- a/examples/contracts/cw20-base/src/minting.rs
+++ b/examples/contracts/cw20-base/src/minting.rs
@@ -2,8 +2,9 @@ use crate::contract::{Cw20Base, MinterData};
 use crate::error::ContractError;
 use cw20_minting::responses::MinterResponse;
 use cw20_minting::Cw20Minting;
+use sylvia::ctx::{ExecCtx, QueryCtx};
 use sylvia::cw_std::{Response, StdResult, Uint128};
-use sylvia::types::{CustomMsg, CustomQuery, ExecCtx, QueryCtx};
+use sylvia::types::{CustomMsg, CustomQuery};
 
 impl<E, Q> Cw20Minting for Cw20Base<E, Q>
 where
diff --git a/examples/contracts/entry-points-overriding/Cargo.toml b/examples/contracts/entry-points-overriding/Cargo.toml
index db005f2..347b6db 100644
--- a/examples/contracts/entry-points-overriding/Cargo.toml
+++ b/examples/contracts/entry-points-overriding/Cargo.toml
@@ -18,7 +18,7 @@ mt = ["library", "sylvia/mt"]
 [dependencies]
 cw-storage-plus = { workspace = true }
 cw-utils = { workspace = true }
-sylvia = { path = "../../../sylvia" }
+sylvia = { workspace = true }
 
 [dev-dependencies]
-sylvia = { path = "../../../sylvia", features = ["mt"] }
+sylvia = { workspace = true, features = ["mt"] }
diff --git a/examples/contracts/entry-points-overriding/src/contract.rs b/examples/contracts/entry-points-overriding/src/contract.rs
index 6d4a7b1..b19f099 100644
--- a/examples/contracts/entry-points-overriding/src/contract.rs
+++ b/examples/contracts/entry-points-overriding/src/contract.rs
@@ -1,7 +1,7 @@
 use cw_storage_plus::Item;
 use sylvia::contract;
+use sylvia::ctx::{ExecCtx, InstantiateCtx, QueryCtx};
 use sylvia::cw_std::{Response, StdError, StdResult};
-use sylvia::types::{ExecCtx, InstantiateCtx, QueryCtx};
 
 #[cfg(not(feature = "library"))]
 use sylvia::entry_points;
diff --git a/examples/contracts/entry-points-overriding/src/messages.rs b/examples/contracts/entry-points-overriding/src/messages.rs
index 0d85f43..ef40280 100644
--- a/examples/contracts/entry-points-overriding/src/messages.rs
+++ b/examples/contracts/entry-points-overriding/src/messages.rs
@@ -1,6 +1,6 @@
+use sylvia::ctx::ExecCtx;
 use sylvia::cw_schema::cw_serde;
 use sylvia::cw_std::{DepsMut, Env, MessageInfo, Response, StdError, StdResult};
-use sylvia::types::ExecCtx;
 
 use crate::contract::sv::ContractExecMsg;
 use crate::contract::CounterContract;
diff --git a/examples/contracts/generic_contract/Cargo.toml b/examples/contracts/generic_contract/Cargo.toml
index b4581ca..63cd5a9 100644
--- a/examples/contracts/generic_contract/Cargo.toml
+++ b/examples/contracts/generic_contract/Cargo.toml
@@ -18,10 +18,10 @@ mt = ["library", "sylvia/mt"]
 [dependencies]
 cw-storage-plus = { workspace = true }
 cw-utils = { workspace = true }
-sylvia = { path = "../../../sylvia" }
+sylvia = { workspace = true }
 cw1 = { path = "../../interfaces/cw1" }
 generic = { path = "../../interfaces/generic" }
 custom-and-generic = { path = "../../interfaces/custom-and-generic" }
 
 [dev-dependencies]
-sylvia = { path = "../../../sylvia", features = ["mt"] }
+sylvia = { workspace = true, features = ["mt"] }
diff --git a/examples/contracts/generic_contract/src/contract.rs b/examples/contracts/generic_contract/src/contract.rs
index 8d15a69..481adcd 100644
--- a/examples/contracts/generic_contract/src/contract.rs
+++ b/examples/contracts/generic_contract/src/contract.rs
@@ -1,8 +1,9 @@
 use cw_storage_plus::Item;
 use sylvia::contract;
-use sylvia::cw_std::{Reply, Response, StdResult};
+use sylvia::ctx::{ExecCtx, InstantiateCtx, MigrateCtx, QueryCtx, ReplyCtx, SudoCtx};
+use sylvia::cw_std::{Binary, Response, StdResult, SubMsgResult};
 use sylvia::serde::Deserialize;
-use sylvia::types::{CustomMsg, ExecCtx, InstantiateCtx, MigrateCtx, QueryCtx, ReplyCtx, SudoCtx};
+use sylvia::types::CustomMsg;
 
 #[cfg(not(feature = "library"))]
 use sylvia::entry_points;
@@ -53,6 +54,7 @@ pub struct GenericContract<
 #[sv::messages(generic as Generic: custom(msg, query))]
 #[sv::messages(custom_and_generic as CustomAndGeneric)]
 #[sv::custom(msg=SvCustomMsg, query=SvCustomQuery)]
+#[sv::features(replies)]
 impl<
         InstantiateT,
         Exec1T,
@@ -185,7 +187,8 @@ where
     fn reply(
         &self,
         _ctx: ReplyCtx<SvCustomQuery>,
-        _reply: Reply,
+        _result: SubMsgResult,
+        _payload: Binary,
     ) -> StdResult<Response<SvCustomMsg>> {
         Ok(Response::new())
     }
diff --git a/examples/contracts/generic_contract/src/custom_and_generic.rs b/examples/contracts/generic_contract/src/custom_and_generic.rs
index ad1d06c..d747132 100644
--- a/examples/contracts/generic_contract/src/custom_and_generic.rs
+++ b/examples/contracts/generic_contract/src/custom_and_generic.rs
@@ -1,6 +1,6 @@
 use custom_and_generic::CustomAndGeneric;
+use sylvia::ctx::{ExecCtx, QueryCtx, SudoCtx};
 use sylvia::cw_std::{CosmosMsg, Response, StdError, StdResult};
-use sylvia::types::{ExecCtx, QueryCtx, SudoCtx};
 
 use crate::contract::{GenericContract, SvCustomMsg, SvCustomQuery};
 
diff --git a/examples/contracts/generic_contract/src/cw1.rs b/examples/contracts/generic_contract/src/cw1.rs
index 9d51212..00a918d 100644
--- a/examples/contracts/generic_contract/src/cw1.rs
+++ b/examples/contracts/generic_contract/src/cw1.rs
@@ -1,7 +1,7 @@
 use crate::contract::GenericContract;
 use cw1::{CanExecuteResp, Cw1};
+use sylvia::ctx::{ExecCtx, QueryCtx};
 use sylvia::cw_std::{CosmosMsg, Empty, Response, StdError, StdResult};
-use sylvia::types::{ExecCtx, QueryCtx};
 
 impl<
         InstantiateT,
diff --git a/examples/contracts/generic_contract/src/generic.rs b/examples/contracts/generic_contract/src/generic.rs
index 1a7a396..0ba40e9 100644
--- a/examples/contracts/generic_contract/src/generic.rs
+++ b/examples/contracts/generic_contract/src/generic.rs
@@ -1,7 +1,8 @@
 use generic::Generic;
+use sylvia::ctx::{ExecCtx, QueryCtx, SudoCtx};
 use sylvia::cw_std::{CosmosMsg, Response, StdError, StdResult};
 use sylvia::serde::Deserialize;
-use sylvia::types::{CustomMsg, ExecCtx, QueryCtx, SudoCtx};
+use sylvia::types::CustomMsg;
 
 use crate::contract::{GenericContract, SvCustomMsg};
 
diff --git a/examples/contracts/generic_iface_on_contract/Cargo.toml b/examples/contracts/generic_iface_on_contract/Cargo.toml
index a164a7e..4ccb0a2 100644
--- a/examples/contracts/generic_iface_on_contract/Cargo.toml
+++ b/examples/contracts/generic_iface_on_contract/Cargo.toml
@@ -18,10 +18,10 @@ mt = ["library", "sylvia/mt"]
 [dependencies]
 cw-storage-plus = { workspace = true }
 cw-utils = { workspace = true }
-sylvia = { path = "../../../sylvia" }
+sylvia = { workspace = true }
 cw1 = { path = "../../interfaces/cw1" }
 generic = { path = "../../interfaces/generic" }
 custom-and-generic = { path = "../../interfaces/custom-and-generic" }
 
 [dev-dependencies]
-sylvia = { path = "../../../sylvia", features = ["mt"] }
+sylvia = { workspace = true, features = ["mt"] }
diff --git a/examples/contracts/generic_iface_on_contract/src/contract.rs b/examples/contracts/generic_iface_on_contract/src/contract.rs
index 8440a49..0a8c10f 100644
--- a/examples/contracts/generic_iface_on_contract/src/contract.rs
+++ b/examples/contracts/generic_iface_on_contract/src/contract.rs
@@ -1,6 +1,6 @@
 use sylvia::contract;
+use sylvia::ctx::InstantiateCtx;
 use sylvia::cw_std::{Response, StdResult};
-use sylvia::types::InstantiateCtx;
 
 #[cfg(not(feature = "library"))]
 use sylvia::entry_points;
diff --git a/examples/contracts/generic_iface_on_contract/src/custom_and_generic.rs b/examples/contracts/generic_iface_on_contract/src/custom_and_generic.rs
index 11dcb6c..aa9fc8c 100644
--- a/examples/contracts/generic_iface_on_contract/src/custom_and_generic.rs
+++ b/examples/contracts/generic_iface_on_contract/src/custom_and_generic.rs
@@ -1,7 +1,7 @@
 use crate::contract::{SvCustomMsg, SvCustomQuery};
 use custom_and_generic::CustomAndGeneric;
+use sylvia::ctx::{ExecCtx, QueryCtx, SudoCtx};
 use sylvia::cw_std::{CosmosMsg, Response, StdError, StdResult};
-use sylvia::types::{ExecCtx, QueryCtx, SudoCtx};
 
 impl CustomAndGeneric for crate::contract::NonGenericContract {
     type Error = StdError;
diff --git a/examples/contracts/generic_iface_on_contract/src/cw1.rs b/examples/contracts/generic_iface_on_contract/src/cw1.rs
index 303e00a..2339ad7 100644
--- a/examples/contracts/generic_iface_on_contract/src/cw1.rs
+++ b/examples/contracts/generic_iface_on_contract/src/cw1.rs
@@ -1,6 +1,6 @@
 use cw1::{CanExecuteResp, Cw1};
+use sylvia::ctx::{ExecCtx, QueryCtx};
 use sylvia::cw_std::{CosmosMsg, Empty, Response, StdError, StdResult};
-use sylvia::types::{ExecCtx, QueryCtx};
 
 impl Cw1 for crate::contract::NonGenericContract {
     type Error = StdError;
diff --git a/examples/contracts/generic_iface_on_contract/src/generic.rs b/examples/contracts/generic_iface_on_contract/src/generic.rs
index 0016361..d8fae81 100644
--- a/examples/contracts/generic_iface_on_contract/src/generic.rs
+++ b/examples/contracts/generic_iface_on_contract/src/generic.rs
@@ -1,6 +1,6 @@
 use generic::Generic;
+use sylvia::ctx::{ExecCtx, QueryCtx, SudoCtx};
 use sylvia::cw_std::{CosmosMsg, Response, StdError, StdResult};
-use sylvia::types::{ExecCtx, QueryCtx, SudoCtx};
 
 use crate::contract::SvCustomMsg;
 
diff --git a/examples/contracts/generics_forwarded/Cargo.toml b/examples/contracts/generics_forwarded/Cargo.toml
index 692e243..4c1b48b 100644
--- a/examples/contracts/generics_forwarded/Cargo.toml
+++ b/examples/contracts/generics_forwarded/Cargo.toml
@@ -18,11 +18,11 @@ mt = ["library", "sylvia/mt"]
 [dependencies]
 cw-storage-plus = { workspace = true }
 cw-utils = { workspace = true }
-sylvia = { path = "../../../sylvia" }
+sylvia = { workspace = true }
 generic = { path = "../../interfaces/generic" }
 custom-and-generic = { path = "../../interfaces/custom-and-generic/" }
 cw1 = { path = "../../interfaces/cw1/" }
 thiserror = { workspace = true }
 
 [dev-dependencies]
-sylvia = { path = "../../../sylvia", features = ["mt"] }
+sylvia = { workspace = true, features = ["mt"] }
diff --git a/examples/contracts/generics_forwarded/src/contract.rs b/examples/contracts/generics_forwarded/src/contract.rs
index b21e516..6b931ac 100644
--- a/examples/contracts/generics_forwarded/src/contract.rs
+++ b/examples/contracts/generics_forwarded/src/contract.rs
@@ -1,11 +1,12 @@
+#![allow(clippy::type_complexity)]
+
 use crate::error::ContractError;
 use cw_storage_plus::Item;
 use sylvia::contract;
-use sylvia::cw_std::{Reply, Response};
+use sylvia::ctx::{ExecCtx, InstantiateCtx, MigrateCtx, QueryCtx, ReplyCtx, SudoCtx};
+use sylvia::cw_std::{Binary, Response, SubMsgResult};
 use sylvia::serde::Deserialize;
-use sylvia::types::{
-    CustomMsg, CustomQuery, ExecCtx, InstantiateCtx, MigrateCtx, QueryCtx, ReplyCtx, SudoCtx,
-};
+use sylvia::types::{CustomMsg, CustomQuery};
 
 #[sylvia::cw_schema::cw_serde(crate = "sylvia::cw_schema")]
 pub struct SvCustomMsg;
@@ -32,7 +33,6 @@ pub struct GenericsForwardedContract<
     FieldT,
 > {
     _field: Item<FieldT>,
-    #[allow(clippy::type_complexity)]
     _phantom: std::marker::PhantomData<(
         InstantiateT,
         Exec1T,
@@ -57,6 +57,7 @@ pub struct GenericsForwardedContract<
 #[sv::messages(cw1 as Cw1: custom(msg, query))]
 #[sv::messages(custom_and_generic as CustomAndGeneric)]
 #[sv::custom(msg=CustomMsgT, query=CustomQueryT)]
+#[sv::features(replies)]
 impl<
         InstantiateT,
         Exec1T,
@@ -195,7 +196,8 @@ where
     fn reply(
         &self,
         _ctx: ReplyCtx<CustomQueryT>,
-        _reply: Reply,
+        _result: SubMsgResult,
+        _payload: Binary,
     ) -> Result<Response<CustomMsgT>, ContractError> {
         Ok(Response::new())
     }
diff --git a/examples/contracts/generics_forwarded/src/custom_and_generic.rs b/examples/contracts/generics_forwarded/src/custom_and_generic.rs
index d793168..174d2b9 100644
--- a/examples/contracts/generics_forwarded/src/custom_and_generic.rs
+++ b/examples/contracts/generics_forwarded/src/custom_and_generic.rs
@@ -1,7 +1,8 @@
 use custom_and_generic::CustomAndGeneric;
+use sylvia::ctx::{ExecCtx, QueryCtx, SudoCtx};
 use sylvia::cw_std::{CosmosMsg, Response};
 use sylvia::serde::Deserialize;
-use sylvia::types::{CustomMsg, CustomQuery, ExecCtx, QueryCtx, SudoCtx};
+use sylvia::types::{CustomMsg, CustomQuery};
 
 use crate::contract::{GenericsForwardedContract, SvCustomMsg};
 use crate::error::ContractError;
diff --git a/examples/contracts/generics_forwarded/src/cw1.rs b/examples/contracts/generics_forwarded/src/cw1.rs
index 86ea371..bbddb54 100644
--- a/examples/contracts/generics_forwarded/src/cw1.rs
+++ b/examples/contracts/generics_forwarded/src/cw1.rs
@@ -1,9 +1,10 @@
 use cw1::{CanExecuteResp, Cw1};
+use sylvia::ctx::{ExecCtx, QueryCtx};
 use sylvia::cw_schema::schemars::JsonSchema;
 use sylvia::cw_std::{CosmosMsg, CustomMsg, Empty, Response, StdResult};
 use sylvia::serde::de::DeserializeOwned;
 use sylvia::serde::Deserialize;
-use sylvia::types::{CustomQuery, ExecCtx, QueryCtx};
+use sylvia::types::CustomQuery;
 
 use crate::contract::GenericsForwardedContract;
 use crate::error::ContractError;
diff --git a/examples/contracts/generics_forwarded/src/generic.rs b/examples/contracts/generics_forwarded/src/generic.rs
index 553aa61..3456d25 100644
--- a/examples/contracts/generics_forwarded/src/generic.rs
+++ b/examples/contracts/generics_forwarded/src/generic.rs
@@ -1,7 +1,8 @@
 use generic::Generic;
+use sylvia::ctx::{ExecCtx, QueryCtx, SudoCtx};
 use sylvia::cw_std::{CosmosMsg, Response};
 use sylvia::serde::Deserialize;
-use sylvia::types::{CustomMsg, CustomQuery, ExecCtx, QueryCtx, SudoCtx};
+use sylvia::types::{CustomMsg, CustomQuery};
 
 use crate::contract::{GenericsForwardedContract, SvCustomMsg};
 use crate::error::ContractError;
diff --git a/examples/interfaces/custom-and-generic/Cargo.toml b/examples/interfaces/custom-and-generic/Cargo.toml
index 9604388..8a49f29 100644
--- a/examples/interfaces/custom-and-generic/Cargo.toml
+++ b/examples/interfaces/custom-and-generic/Cargo.toml
@@ -12,7 +12,7 @@ homepage = "https://cosmwasm.com"
 mt = ["sylvia/mt"]
 
 [dependencies]
-sylvia = { path = "../../../sylvia" }
+sylvia = { workspace = true }
 
 [dev-dependencies]
-sylvia = { path = "../../../sylvia", features = ["mt"] }
+sylvia = { workspace = true, features = ["mt"] }
diff --git a/examples/interfaces/custom-and-generic/src/lib.rs b/examples/interfaces/custom-and-generic/src/lib.rs
index 5eece85..1569dde 100644
--- a/examples/interfaces/custom-and-generic/src/lib.rs
+++ b/examples/interfaces/custom-and-generic/src/lib.rs
@@ -1,7 +1,7 @@
+use sylvia::ctx::{ExecCtx, QueryCtx, SudoCtx};
 use sylvia::cw_std::{CosmosMsg, Response, StdError};
-
 use sylvia::interface;
-use sylvia::types::{CustomMsg, CustomQuery, ExecCtx, QueryCtx, SudoCtx};
+use sylvia::types::{CustomMsg, CustomQuery};
 
 #[interface]
 pub trait CustomAndGeneric {
diff --git a/examples/interfaces/cw1/Cargo.toml b/examples/interfaces/cw1/Cargo.toml
index 59c0b6c..17ba39e 100644
--- a/examples/interfaces/cw1/Cargo.toml
+++ b/examples/interfaces/cw1/Cargo.toml
@@ -12,7 +12,7 @@ homepage = "https://cosmwasm.com"
 mt = ["sylvia/mt"]
 
 [dependencies]
-sylvia = { path = "../../../sylvia" }
+sylvia = { workspace = true }
 
 [dev-dependencies]
-sylvia = { path = "../../../sylvia", features = ["mt"] }
+sylvia = { workspace = true, features = ["mt"] }
diff --git a/examples/interfaces/cw1/src/lib.rs b/examples/interfaces/cw1/src/lib.rs
index 97d85d6..0774a32 100644
--- a/examples/interfaces/cw1/src/lib.rs
+++ b/examples/interfaces/cw1/src/lib.rs
@@ -1,6 +1,7 @@
+use sylvia::ctx::{ExecCtx, QueryCtx};
 use sylvia::cw_std::{CosmosMsg, Response, StdError, StdResult};
 use sylvia::serde::{Deserialize, Serialize};
-use sylvia::types::{CustomMsg, CustomQuery, ExecCtx, QueryCtx};
+use sylvia::types::{CustomMsg, CustomQuery};
 use sylvia::{interface, schemars};
 
 #[derive(
diff --git a/examples/interfaces/cw20-allowances/Cargo.toml b/examples/interfaces/cw20-allowances/Cargo.toml
index 9d09238..241f349 100644
--- a/examples/interfaces/cw20-allowances/Cargo.toml
+++ b/examples/interfaces/cw20-allowances/Cargo.toml
@@ -12,8 +12,8 @@ homepage = "https://cosmwasm.com"
 mt = ["sylvia/mt"]
 
 [dependencies]
-sylvia = { path = "../../../sylvia" }
+sylvia = { workspace = true }
 cw-utils = { workspace = true }
 
 [dev-dependencies]
-sylvia = { path = "../../../sylvia", features = ["mt"] }
+sylvia = { workspace = true, features = ["mt"] }
diff --git a/examples/interfaces/cw20-allowances/src/lib.rs b/examples/interfaces/cw20-allowances/src/lib.rs
index 1609592..5c723f7 100644
--- a/examples/interfaces/cw20-allowances/src/lib.rs
+++ b/examples/interfaces/cw20-allowances/src/lib.rs
@@ -4,9 +4,10 @@ use cw_utils::Expiration;
 use responses::{
     AllAccountsResponse, AllAllowancesResponse, AllSpenderAllowancesResponse, AllowanceResponse,
 };
+use sylvia::ctx::{ExecCtx, QueryCtx};
 use sylvia::cw_std::{Binary, Response, StdError, StdResult, Uint128};
 use sylvia::interface;
-use sylvia::types::{CustomMsg, CustomQuery, ExecCtx, QueryCtx};
+use sylvia::types::{CustomMsg, CustomQuery};
 
 #[interface]
 pub trait Cw20Allowances {
diff --git a/examples/interfaces/cw20-marketing/Cargo.toml b/examples/interfaces/cw20-marketing/Cargo.toml
index 937490d..26afd60 100644
--- a/examples/interfaces/cw20-marketing/Cargo.toml
+++ b/examples/interfaces/cw20-marketing/Cargo.toml
@@ -12,7 +12,7 @@ homepage = "https://cosmwasm.com"
 mt = ["sylvia/mt"]
 
 [dependencies]
-sylvia = { path = "../../../sylvia" }
+sylvia = { workspace = true }
 
 [dev-dependencies]
-sylvia = { path = "../../../sylvia", features = ["mt"] }
+sylvia = { workspace = true, features = ["mt"] }
diff --git a/examples/interfaces/cw20-marketing/src/lib.rs b/examples/interfaces/cw20-marketing/src/lib.rs
index 81f0500..ece9321 100644
--- a/examples/interfaces/cw20-marketing/src/lib.rs
+++ b/examples/interfaces/cw20-marketing/src/lib.rs
@@ -1,10 +1,11 @@
 pub mod responses;
 
 use responses::{DownloadLogoResponse, MarketingInfoResponse};
+use sylvia::ctx::{ExecCtx, QueryCtx};
 use sylvia::cw_schema::cw_serde;
 use sylvia::cw_std::{Binary, Response, StdError, StdResult};
 use sylvia::interface;
-use sylvia::types::{CustomMsg, CustomQuery, ExecCtx, QueryCtx};
+use sylvia::types::{CustomMsg, CustomQuery};
 
 /// This is used for uploading logo data, or setting it in InstantiateData
 #[cw_serde(crate = "sylvia::cw_schema")]
diff --git a/examples/interfaces/cw20-minting/Cargo.toml b/examples/interfaces/cw20-minting/Cargo.toml
index 7e06de5..a285a6a 100644
--- a/examples/interfaces/cw20-minting/Cargo.toml
+++ b/examples/interfaces/cw20-minting/Cargo.toml
@@ -12,7 +12,7 @@ homepage = "https://cosmwasm.com"
 mt = ["sylvia/mt"]
 
 [dependencies]
-sylvia = { path = "../../../sylvia" }
+sylvia = { workspace = true }
 
 [dev-dependencies]
-sylvia = { path = "../../../sylvia", features = ["mt"] }
+sylvia = { workspace = true, features = ["mt"] }
diff --git a/examples/interfaces/cw20-minting/src/lib.rs b/examples/interfaces/cw20-minting/src/lib.rs
index 9bfe86e..f033f2a 100644
--- a/examples/interfaces/cw20-minting/src/lib.rs
+++ b/examples/interfaces/cw20-minting/src/lib.rs
@@ -1,9 +1,10 @@
 pub mod responses;
 
 use responses::MinterResponse;
+use sylvia::ctx::{ExecCtx, QueryCtx};
 use sylvia::cw_std::{Response, StdError, StdResult, Uint128};
 use sylvia::interface;
-use sylvia::types::{CustomMsg, CustomQuery, ExecCtx, QueryCtx};
+use sylvia::types::{CustomMsg, CustomQuery};
 
 #[interface]
 pub trait Cw20Minting {
diff --git a/examples/interfaces/cw4/Cargo.toml b/examples/interfaces/cw4/Cargo.toml
index 5267513..4292f38 100644
--- a/examples/interfaces/cw4/Cargo.toml
+++ b/examples/interfaces/cw4/Cargo.toml
@@ -12,7 +12,7 @@ homepage = "https://cosmwasm.com"
 mt = ["sylvia/mt"]
 
 [dependencies]
-sylvia = { path = "../../../sylvia" }
+sylvia = { workspace = true }
 
 [dev-dependencies]
-sylvia = { path = "../../../sylvia", features = ["mt"] }
+sylvia = { workspace = true, features = ["mt"] }
diff --git a/examples/interfaces/cw4/src/lib.rs b/examples/interfaces/cw4/src/lib.rs
index 1194ed6..cc53115 100644
--- a/examples/interfaces/cw4/src/lib.rs
+++ b/examples/interfaces/cw4/src/lib.rs
@@ -3,9 +3,10 @@ pub mod responses;
 use responses::{
     AdminResponse, HooksResponse, MemberListResponse, MemberResponse, TotalWeightResponse,
 };
+use sylvia::ctx::{ExecCtx, QueryCtx};
 use sylvia::cw_std::{Response, StdError};
 use sylvia::interface;
-use sylvia::types::{CustomMsg, CustomQuery, ExecCtx, QueryCtx};
+use sylvia::types::{CustomMsg, CustomQuery};
 
 #[interface]
 pub trait Cw4 {
diff --git a/examples/interfaces/generic/Cargo.toml b/examples/interfaces/generic/Cargo.toml
index 817ad94..cfd3142 100644
--- a/examples/interfaces/generic/Cargo.toml
+++ b/examples/interfaces/generic/Cargo.toml
@@ -12,7 +12,7 @@ homepage = "https://cosmwasm.com"
 mt = ["sylvia/mt"]
 
 [dependencies]
-sylvia = { path = "../../../sylvia" }
+sylvia = { workspace = true }
 
 [dev-dependencies]
-sylvia = { path = "../../../sylvia", features = ["mt"] }
+sylvia = { workspace = true, features = ["mt"] }
diff --git a/examples/interfaces/generic/src/lib.rs b/examples/interfaces/generic/src/lib.rs
index c836ca0..2c7690d 100644
--- a/examples/interfaces/generic/src/lib.rs
+++ b/examples/interfaces/generic/src/lib.rs
@@ -1,7 +1,8 @@
 use sylvia::cw_std::{CosmosMsg, Response, StdError};
 
+use sylvia::ctx::{ExecCtx, QueryCtx, SudoCtx};
 use sylvia::interface;
-use sylvia::types::{CustomMsg, ExecCtx, QueryCtx, SudoCtx};
+use sylvia::types::CustomMsg;
 
 #[interface]
 #[sv::custom(msg=sylvia::cw_std::Empty, query=sylvia::cw_std::Empty)]
diff --git a/examples/interfaces/whitelist/Cargo.toml b/examples/interfaces/whitelist/Cargo.toml
index 409f213..a18eda9 100644
--- a/examples/interfaces/whitelist/Cargo.toml
+++ b/examples/interfaces/whitelist/Cargo.toml
@@ -12,7 +12,7 @@ homepage = "https://cosmwasm.com"
 mt = ["sylvia/mt"]
 
 [dependencies]
-sylvia = { path = "../../../sylvia" }
+sylvia = { workspace = true }
 
 [dev-dependencies]
-sylvia = { path = "../../../sylvia", features = ["mt"] }
+sylvia = { workspace = true, features = ["mt"] }
diff --git a/examples/interfaces/whitelist/src/lib.rs b/examples/interfaces/whitelist/src/lib.rs
index 472a433..bc90a78 100644
--- a/examples/interfaces/whitelist/src/lib.rs
+++ b/examples/interfaces/whitelist/src/lib.rs
@@ -1,7 +1,8 @@
 use responses::AdminListResponse;
+use sylvia::ctx::{ExecCtx, QueryCtx};
 use sylvia::cw_std::{Response, StdError, StdResult};
 use sylvia::interface;
-use sylvia::types::{CustomMsg, CustomQuery, ExecCtx, QueryCtx};
+use sylvia::types::{CustomMsg, CustomQuery};
 
 pub mod responses;
 
diff --git a/sylvia-derive/Cargo.toml b/sylvia-derive/Cargo.toml
index 4918d60..c28dd22 100644
--- a/sylvia-derive/Cargo.toml
+++ b/sylvia-derive/Cargo.toml
@@ -19,17 +19,17 @@ cosmwasm_1_2 = []
 proc-macro = true
 
 [dependencies]
-syn = { version = "2.0.72", features = [
+syn = { version = "2.0.87", features = [
     "fold",
     "visit",
     "full",
     "extra-traits",
 ] }
-quote = "1.0.36"
-proc-macro2 = "1.0.86"
+quote = "1.0.37"
+proc-macro2 = "1.0.89"
 convert_case = "0.6.0"
 proc-macro-error = "1.0.4"
-proc-macro-crate = "3.1.0"
+proc-macro-crate = "3.2.0"
 itertools = "0.13.0"
 
 [dev-dependencies]
diff --git a/sylvia-derive/src/contract.rs b/sylvia-derive/src/contract.rs
index ac42877..229bab9 100644
--- a/sylvia-derive/src/contract.rs
+++ b/sylvia-derive/src/contract.rs
@@ -1,7 +1,9 @@
 use communication::api::Api;
 use communication::enum_msg::EnumMessage;
 use communication::executor::Executor;
+use communication::instantiate_builder::InstantiateBuilder;
 use communication::querier::Querier;
+use communication::reply::Reply;
 use communication::struct_msg::StructMessage;
 use communication::wrapper_msg::GlueMessage;
 use mt::MtHelpers;
@@ -9,6 +11,7 @@ use proc_macro2::TokenStream;
 use quote::quote;
 use syn::{GenericParam, ItemImpl};
 
+use crate::parser::attributes::features::SylviaFeatures;
 use crate::parser::attributes::msg::MsgType;
 use crate::parser::variant_descs::AsVariantDescs;
 use crate::parser::{
@@ -44,6 +47,7 @@ pub struct ContractInput<'a> {
     custom: Custom,
     override_entry_points: Vec<OverrideEntryPoint>,
     interfaces: Interfaces,
+    sv_features: SylviaFeatures,
 }
 
 impl<'a> ContractInput<'a> {
@@ -54,6 +58,7 @@ impl<'a> ContractInput<'a> {
         let parsed_attrs = ParsedSylviaAttributes::new(item.attrs.iter());
         let error = parsed_attrs.error_attrs.unwrap_or_default();
         let custom = parsed_attrs.custom_attr.unwrap_or_default();
+        let sv_features = parsed_attrs.sv_features;
         let override_entry_points = parsed_attrs.override_entry_point_attrs;
         let interfaces = Interfaces::new(item);
 
@@ -64,6 +69,7 @@ impl<'a> ContractInput<'a> {
             custom,
             override_entry_points,
             interfaces,
+            sv_features,
         }
     }
 
@@ -76,23 +82,12 @@ impl<'a> ContractInput<'a> {
             ..
         } = self;
         let multitest_helpers = self.emit_multitest_helpers();
-
-        let executor_variants = MsgVariants::new(item.as_variants(), MsgType::Exec, &[], &None);
-        let querier_variants = MsgVariants::new(item.as_variants(), MsgType::Query, &[], &None);
-        let executor = Executor::new(
-            item.generics.clone(),
-            *item.self_ty.clone(),
-            executor_variants,
-        )
-        .emit();
-        let querier = Querier::new(
-            item.generics.clone(),
-            *item.self_ty.clone(),
-            querier_variants,
-        )
-        .emit();
         let messages = self.emit_messages();
         let contract_api = Api::new(item, generics, custom).emit();
+        let querier = self.emit_querier();
+        let executor = self.emit_executor();
+        let reply = self.emit_reply();
+        let instantiate_builder = self.emit_instantiate_builder_trait();
 
         quote! {
             pub mod sv {
@@ -106,7 +101,11 @@ impl<'a> ContractInput<'a> {
 
                 #executor
 
+                #reply
+
                 #contract_api
+
+                #instantiate_builder
             }
         }
     }
@@ -175,4 +174,48 @@ impl<'a> ContractInput<'a> {
         let generic_params = &self.generics;
         MtHelpers::new(item, generic_params, custom, override_entry_points.clone()).emit()
     }
+
+    fn emit_executor(&self) -> TokenStream {
+        let item = self.item;
+        let variants = MsgVariants::new(item.as_variants(), MsgType::Exec, &[], &None);
+
+        Executor::new(item.generics.clone(), *item.self_ty.clone(), variants).emit()
+    }
+    fn emit_querier(&self) -> TokenStream {
+        let item = self.item;
+        let variants = MsgVariants::new(item.as_variants(), MsgType::Query, &[], &None);
+
+        Querier::new(item.generics.clone(), *item.self_ty.clone(), variants).emit()
+    }
+
+    fn emit_reply(&self) -> TokenStream {
+        if !self.sv_features.replies {
+            return quote! {};
+        }
+
+        let variants = MsgVariants::new(self.item.as_variants(), MsgType::Reply, &[], &None);
+
+        Reply::new(self.item, &self.generics, &variants).emit()
+    }
+
+    fn emit_instantiate_builder_trait(&self) -> TokenStream {
+        let variants = MsgVariants::new(
+            self.item.as_variants(),
+            MsgType::Instantiate,
+            &self.generics,
+            &self.item.generics.where_clause,
+        );
+        let where_clause = variants.where_clause();
+
+        match variants.get_only_variant() {
+            Some(variant) => InstantiateBuilder::new(
+                *self.item.self_ty.clone(),
+                variants.used_generics(),
+                &where_clause,
+                variant,
+            )
+            .emit(),
+            None => quote! {},
+        }
+    }
 }
diff --git a/sylvia-derive/src/contract/communication/api.rs b/sylvia-derive/src/contract/communication/api.rs
index 48d4907..92313ce 100644
--- a/sylvia-derive/src/contract/communication/api.rs
+++ b/sylvia-derive/src/contract/communication/api.rs
@@ -78,7 +78,6 @@ impl<'a> Api<'a> {
             sudo_variants,
             generics,
             custom,
-            ..
         } = self;
 
         let where_clause = &source.generics.where_clause;
diff --git a/sylvia-derive/src/contract/communication/instantiate_builder.rs b/sylvia-derive/src/contract/communication/instantiate_builder.rs
new file mode 100644
index 0000000..aee4bf5
--- /dev/null
+++ b/sylvia-derive/src/contract/communication/instantiate_builder.rs
@@ -0,0 +1,68 @@
+use convert_case::Case;
+use proc_macro2::{Ident, TokenStream};
+use quote::quote;
+use syn::{GenericParam, Type, WhereClause};
+
+use crate::crate_module;
+use crate::parser::MsgType;
+use crate::types::msg_variant::MsgVariant;
+use crate::utils::{get_ident_from_type, SvCasing};
+
+pub struct InstantiateBuilder<'a> {
+    contract: Type,
+    used_generics: &'a [&'a GenericParam],
+    where_clause: &'a Option<WhereClause>,
+    instantiate_variant: &'a MsgVariant<'a>,
+}
+
+impl<'a> InstantiateBuilder<'a> {
+    pub fn new(
+        contract: Type,
+        used_generics: &'a [&'a syn::GenericParam],
+        where_clause: &'a Option<WhereClause>,
+        instantiate_variant: &'a MsgVariant<'a>,
+    ) -> Self {
+        Self {
+            contract,
+            used_generics,
+            where_clause,
+            instantiate_variant,
+        }
+    }
+
+    pub fn emit(&self) -> TokenStream {
+        let sylvia = crate_module();
+        let Self {
+            contract,
+            used_generics,
+            where_clause,
+            instantiate_variant,
+        } = self;
+
+        let contract_name = get_ident_from_type(contract);
+        let msg_type = MsgType::Instantiate;
+
+        let trait_name = Ident::new(
+            &format!("{}InstantiateBuilder", &contract_name.to_string()),
+            contract_name.span(),
+        );
+        let method_name = contract_name.to_case(Case::Snake);
+        let fields_names = instantiate_variant.as_fields_names();
+        let parameters = instantiate_variant.emit_method_field();
+        let msg_name = msg_type.emit_msg_name();
+
+        quote! {
+            pub trait #trait_name {
+                fn #method_name < #(#used_generics),* > (code_id: u64, #(#parameters),* ) -> #sylvia ::cw_std::StdResult < #sylvia ::builder::instantiate::InstantiateBuilder> #where_clause;
+            }
+
+            impl #trait_name for #sylvia ::builder::instantiate::InstantiateBuilder {
+                fn #method_name < #(#used_generics),* > (code_id: u64, #(#parameters,)* ) -> #sylvia ::cw_std::StdResult< #sylvia ::builder::instantiate::InstantiateBuilder> #where_clause {
+                    let msg = #msg_name ::< #(#used_generics),* > ::new( #(#fields_names),* );
+                    let msg = #sylvia ::cw_std::to_json_binary(&msg)?;
+                    Ok( #sylvia ::builder::instantiate::InstantiateBuilder::new(msg, code_id))
+                }
+            }
+        }
+    }
+}
diff --git a/sylvia-derive/src/contract/communication/mod.rs b/sylvia-derive/src/contract/communication/mod.rs
index 012695c..8653ff9 100644
--- a/sylvia-derive/src/contract/communication/mod.rs
+++ b/sylvia-derive/src/contract/communication/mod.rs
@@ -1,6 +1,8 @@
 pub mod api;
 pub mod enum_msg;
 pub mod executor;
+pub mod instantiate_builder;
 pub mod querier;
+pub mod reply;
 pub mod struct_msg;
 pub mod wrapper_msg;
diff --git a/sylvia-derive/src/contract/communication/reply.rs b/sylvia-derive/src/contract/communication/reply.rs
new file mode 100644
index 0000000..e5c1041
--- /dev/null
+++ b/sylvia-derive/src/contract/communication/reply.rs
@@ -0,0 +1,732 @@
+//! Module responsible for generating `Reply` related code.
+//!
+//! Based on methods marked with the `#[sv::msg(reply)]` attribute, this module generates:
+//!     - reply ids for every unique handler,
+//!     - dispatch method that matches over every generated `ReplyId` and dispatches depending on the `ReplyOn`,
+//!     - `SubMsgMethods` trait with method for every reply id.
+
+use convert_case::{Case, Casing};
+use proc_macro2::TokenStream;
+use proc_macro_error::emit_error;
+use quote::quote;
+use syn::{parse_quote, GenericParam, Ident, ItemImpl, Type};
+
+use crate::crate_module;
+use crate::parser::attributes::msg::ReplyOn;
+use crate::parser::{MsgType, ParsedSylviaAttributes};
+use crate::types::msg_field::MsgField;
+use crate::types::msg_variant::{MsgVariant, MsgVariants};
+use crate::utils::emit_turbofish;
+
+const NUMBER_OF_ALLOWED_DATA_FIELDS: usize = 1;
+const NUMBER_OF_ALLOWED_RAW_PAYLOAD_FIELDS: usize = 1;
+
+/// Make sure that there are no additional parameters between ones marked
+/// with `sv::data` and `sv::payload` and after the one marked with `sv::payload`.
+fn assert_no_redundant_params(payload: &[&MsgField]) {
+    let payload_param = payload.iter().enumerate().find(|(_, field)| {
+        ParsedSylviaAttributes::new(field.attrs().iter())
+            .payload
+            .is_some()
+    });
+
+    if payload.len() == NUMBER_OF_ALLOWED_RAW_PAYLOAD_FIELDS {
+        return;
+    }
+
+    let Some((index, payload_param)) = payload_param else {
+        return;
+    };
+
+    if index == 0 {
+        emit_error!(payload[1].name().span(), "Redundant payload parameter.";
+            note = payload_param.name().span() => "Expected no parameters after the parameter marked with `#[sv::payload(raw)]`."
+        )
+    } else {
+        emit_error!(payload[0].name().span(), "Redundant payload parameter.";
+            note = payload_param.name().span() => "Expected no parameters between the parameter marked with `#[sv::data]` and `#[sv::payload(raw)]`."
+        )
+    }
+}
+
+pub struct Reply<'a> {
+    source: &'a ItemImpl,
+    generics: &'a [&'a GenericParam],
+    reply_data: Vec<ReplyData<'a>>,
+    error: Type,
+}
+
+impl<'a> Reply<'a> {
+    pub fn new(
+        source: &'a ItemImpl,
+        generics: &'a [&'a GenericParam],
+        variants: &'a MsgVariants<'a, GenericParam>,
+    ) -> Self {
+        let reply_data = variants.as_reply_data();
+        let parsed_attrs = ParsedSylviaAttributes::new(source.attrs.iter());
+        let error = parsed_attrs.error_attrs.unwrap_or_default().error;
+
+        Self {
+            source,
+            generics,
+            reply_data,
+            error,
+        }
+    }
+
+    pub fn emit(&self) -> TokenStream {
+        let unique_handlers: Vec<_> = self.emit_reply_ids().collect();
+        let dispatch = self.emit_dispatch();
+        let sub_msg_trait = self.emit_sub_msg_trait();
+
+        quote! {
+            #(#unique_handlers)*
+
+            #dispatch
+
+            #sub_msg_trait
+        }
+    }
+
+    /// Generates dispatch method that matches over every generated `ReplyId`
+    /// and dispatches depending on the [`ReplyOn`](crate::parser::attributes::msg::ReplyOn).
+    pub fn emit_dispatch(&self) -> TokenStream {
+        let sylvia = crate_module();
+        let Self {
+            source,
+            generics,
+            reply_data,
+            error,
+            ..
+        } = self;
+
+        let msg_ty = MsgType::Reply;
+        let contract = &source.self_ty;
+        let where_clause = &source.generics.where_clause;
+
+        let custom_query = parse_quote!( < #contract as #sylvia ::types::ContractApi>::CustomQuery);
+        let custom_msg = parse_quote!( < #contract as #sylvia ::types::ContractApi>::CustomMsg);
+        let ctx_params = msg_ty.emit_ctx_params(&custom_query);
+        let ret_type = msg_ty.emit_result_type(&custom_msg, error);
+
+        let match_arms = reply_data
+            .iter()
+            .map(|data| data.emit_match_arms(contract, generics));
+
+        quote! {
+            pub fn dispatch_reply < #(#generics),* >( #ctx_params , msg: #sylvia ::cw_std::Reply, contract: #contract ) -> #ret_type #where_clause {
+                let #sylvia ::cw_std::Reply {
+                    id,
+                    payload,
+                    gas_used,
+                    result,
+                } = msg;
+
+                match id {
+                    #(#match_arms,)*
+                    _ => {
+                        let err_msg = format!("Unknown reply id: {}.", id);
+                        Err( #sylvia ::cw_std::StdError::generic_err(err_msg)).map_err(Into::into)
+                    }
+                }
+            }
+        }
+    }
+
+    /// Generates `ReplyId`s for every unique
+    /// [`reply_handler`](crate::parser::attributes::msg::MsgAttr::reply_handlers) and
+    /// [`function_name`](crate::types::msg_variant::MsgVariant::function_name) not anotated with
+    /// the `#[sv::msg(..)]` attribute with the `handlers` parameter.
+    fn emit_reply_ids(&'a self) -> impl Iterator<Item = TokenStream> + 'a {
+        self.reply_data.iter().enumerate().map(|(id, data)| {
+            let id = id as u64;
+            let reply_id = &data.reply_id;
+            quote! {
+                pub const #reply_id : u64 = #id ;
+            }
+        })
+    }
+
+    /// Generates `SubMsgMethods` trait with method for every reply id.
+    fn emit_sub_msg_trait(&self) -> TokenStream {
+        let Self { reply_data, .. } = self;
+
+        let sylvia = crate_module();
+
+        let methods_declaration = reply_data.iter().map(ReplyData::emit_submsg_trait_method);
+        let submsg_reply_setters = reply_data.iter().map(ReplyData::emit_submsg_setter);
+        let submsg_converters: Vec<_> = reply_data
+            .iter()
+            .map(ReplyData::emit_submsg_converter)
+            .collect();
+
+        quote! {
+            pub trait SubMsgMethods<CustomMsgT> {
+                #(#methods_declaration)*
+            }
+
+            impl<CustomMsgT> SubMsgMethods<CustomMsgT> for #sylvia ::cw_std::SubMsg<CustomMsgT> {
+                #(#submsg_reply_setters)*
+            }
+
+            impl<CustomMsgT> SubMsgMethods<CustomMsgT> for #sylvia ::cw_std::WasmMsg {
+                #(#submsg_converters)*
+            }
+
+            impl<CustomMsgT> SubMsgMethods<CustomMsgT> for #sylvia ::cw_std::CosmosMsg<CustomMsgT> {
+                #(#submsg_converters)*
+            }
+        }
+    }
+}
+
+trait ReplyVariants<'a> {
+    /// Maps to [Vec<ReplyData>].
+    /// Validation for duplicates and overlaps should be done in this method.
+    fn as_reply_data(&self) -> Vec<ReplyData>;
+}
+
+impl<'a> ReplyVariants<'a> for MsgVariants<'a, GenericParam> {
+    fn as_reply_data(&self) -> Vec<ReplyData> {
+        let mut reply_data: Vec<ReplyData> = vec![];
+
+        self.variants()
+            .flat_map(ReplyVariant::as_variant_handlers_pair)
+            .for_each(|(handler,  handler_id)| {
+                let reply_on = handler.msg_attr().reply_on();
+                let reply_id = handler_id.as_reply_id();
+                match reply_data
+                    .iter_mut()
+                    .find(|existing_data| existing_data.reply_id == reply_id)
+                {
+                    Some(existing_data)
+                        if existing_data
+                            .handlers
+                            .iter()
+                            .any(|(_, existing_reply_on)| existing_reply_on.excludes(&reply_on)) =>
+                    {
+                        existing_data.handlers.iter().for_each(
+                            |(existing_function_name, existing_reply_on)| {
+                                let existing_reply_id = &existing_data.reply_id;
+
+                                emit_error!(reply_id.span(), "Duplicated reply handler.";
+                                    note = existing_data.reply_id.span() => format!("Previous definition of handler=`{}` for reply_on=`{}` defined on `fn {}()`", existing_reply_id, existing_reply_on, existing_function_name);
+                                )
+                            },
+                        )
+                    }
+                    Some(existing_data) => existing_data.merge(handler),
+                    None => reply_data.push(ReplyData::new(reply_id, handler,  handler_id)),
+                }
+            });
+
+        reply_data
+    }
+}
+
+/// Maps single reply id with its handlers.
+struct ReplyData<'a> {
+    /// Unique identifier for the reply.
+    pub reply_id: Ident,
+    /// Unique name of the handler from which the [reply_id](ReplyData::reply_id) was constructed.
+    pub handler_id: &'a Ident,
+    /// Methods handling the reply id for the associated reply on.
+    pub handlers: Vec<(&'a Ident, ReplyOn)>,
+    /// Data parameter associated with the handlers.
+    pub data: Option<&'a MsgField<'a>>,
+    /// Payload parameters associated with the handlers.
+    pub payload: Vec<&'a MsgField<'a>>,
+}
+
+impl<'a> ReplyData<'a> {
+    pub fn new(reply_id: Ident, variant: &'a MsgVariant<'a>, handler_id: &'a Ident) -> Self {
+        let data = variant.as_data_field();
+        variant.validate_fields_attributes();
+        let payload = variant.fields().iter();
+        let payload = if data.is_some() || variant.msg_attr().reply_on() != ReplyOn::Success {
+            payload
+                .skip(NUMBER_OF_ALLOWED_DATA_FIELDS)
+                .collect::<Vec<_>>()
+        } else {
+            payload.collect::<Vec<_>>()
+        };
+
+        if payload.is_empty() {
+            emit_error!(variant.name().span(), "Missing payload parameter.";
+                note =  "Expected at least one payload parameter at the end of parameter list."
+            )
+        }
+
+        assert_no_redundant_params(&payload);
+        let method_name = variant.function_name();
+        let reply_on = variant.msg_attr().reply_on();
+
+        Self {
+            reply_id,
+            handler_id,
+            handlers: vec![(method_name, reply_on)],
+            data,
+            payload,
+        }
+    }
+
+    /// Adds second handler to the reply data provdided their payload signature match.
+    pub fn merge(&mut self, new_handler: &'a MsgVariant<'a>) {
+        let (current_method_name, _) = match self.handlers.first() {
+            Some(handler) => handler,
+            _ => return,
+        };
+
+        let new_reply_data = ReplyData::new(self.reply_id.clone(), new_handler, self.handler_id);
+
+        if self.payload.len() != new_reply_data.payload.len() {
+            emit_error!(current_method_name.span(), "Mismatched quantity of method parameters.";
+                note = self.handler_id.span() => format!("Both `{}` handlers should have the same number of parameters.", self.handler_id);
+                note = new_handler.function_name().span() => format!("Previous definition of {} handler.", self.handler_id)
+            );
+        }
+
+        self.payload
+            .iter()
+            .zip(new_reply_data.payload.iter())
+            .for_each(|(current_field, new_field)|
+        {
+            if current_field.ty() != new_field.ty() {
+                emit_error!(current_field.name().span(), "Mismatched parameter in reply handlers.";
+                    note = current_field.name().span() => format!("Parameters for the `{}` handler have to be the same.", self.handler_id);
+                    note = new_field.name().span() => format!("Previous parameter defined for the `{}` handler.", self.handler_id)
+                )
+            }
+        });
+
+        let new_function_name = new_handler.function_name();
+        let new_reply_on = new_handler.msg_attr().reply_on();
+        self.handlers.push((new_function_name, new_reply_on));
+    }
+
+    /// Emits success and error match arms for a single `ReplyId`.
+    fn emit_match_arms(&self, contract: &Type, generics: &[&GenericParam]) -> TokenStream {
+        let reply_id = &self.reply_id;
+        let contract_turbofish = emit_turbofish(contract, generics);
+        let success_match_arm = self.emit_success_match_arm(&contract_turbofish);
+        let error_match_arm = self.emit_error_match_arm(&contract_turbofish);
+
+        quote! {
+            #reply_id => {
+                match result {
+                    #success_match_arm
+                    #error_match_arm
+                }
+            }
+        }
+    }
+
+    /// Emits [cosmwasm_std::ReplyOn] value to be put in the `cosmwasm_std::SubMsg`.
+    /// If both `Success` and `Error` is defined for given `reply_id`, `cosmwasm_std::ReplyOn::Always` is returned.
+    fn emit_cw_reply_on(&self) -> TokenStream {
+        let sylvia = crate_module();
+        let is_always = self
+            .handlers
+            .iter()
+            .any(|(_, reply_on)| reply_on == &ReplyOn::Always);
+        let is_success = self
+            .handlers
+            .iter()
+            .any(|(_, reply_on)| reply_on == &ReplyOn::Success);
+        let is_error = self
+            .handlers
+            .iter()
+            .any(|(_, reply_on)| reply_on == &ReplyOn::Error);
+
+        if is_always || (is_success && is_error) {
+            quote! { #sylvia ::cw_std::ReplyOn::Always }
+        } else if is_success {
+            quote! { #sylvia ::cw_std::ReplyOn::Success }
+        } else {
+            quote! { #sylvia ::cw_std::ReplyOn::Error }
+        }
+    }
+
+    /// Emits method setting reply related fields on the `cosmwasm_std::SubMsg`.
+    fn emit_submsg_setter(&self) -> TokenStream {
+        let sylvia = crate_module();
+        let Self {
+            reply_id,
+            handler_id,
+            payload,
+            ..
+        } = self;
+
+        let method_name = handler_id;
+        let reply_on = self.emit_cw_reply_on();
+        let payload_parameters = payload.iter().map(|field| field.emit_method_field());
+        let payload_serialization = payload.emit_payload_serialization();
+
+        quote! {
+            fn #method_name (self, #(#payload_parameters),* ) -> #sylvia ::cw_std::StdResult< #sylvia ::cw_std::SubMsg<CustomMsgT>> {
+                #payload_serialization
+
+                Ok( #sylvia ::cw_std::SubMsg {
+                    reply_on: #reply_on ,
+                    id: #reply_id ,
+                    payload,
+                    ..self
+                })
+            }
+        }
+    }
+
+    /// Emits method for converting `WasmMsg` or `CosmosMsg` to `SubMsg`.
+    fn emit_submsg_converter(&self) -> TokenStream {
+        let sylvia = crate_module();
+        let Self {
+            reply_id,
+            handler_id,
+            payload,
+            ..
+        } = self;
+
+        let method_name = handler_id;
+        let reply_on = self.emit_cw_reply_on();
+        let payload_parameters = payload.iter().map(|field| field.emit_method_field());
+        let payload_serialization = payload.emit_payload_serialization();
+
+        quote! {
+            fn #method_name (self, #(#payload_parameters),* ) -> #sylvia ::cw_std::StdResult< #sylvia ::cw_std::SubMsg<CustomMsgT>> {
+                #payload_serialization
+
+                Ok( #sylvia ::cw_std::SubMsg {
+                    reply_on: #reply_on ,
+                    id: #reply_id ,
+                    msg: self.into(),
+                    payload,
+                    gas_limit: None,
+                })
+            }
+        }
+    }
+
+    fn emit_submsg_trait_method(&self) -> TokenStream {
+        let sylvia = crate_module();
+        let method_name = &self.handler_id;
+        let payload_parameters = self.payload.iter().map(|field| field.emit_method_field());
+
+        quote! {
+            fn #method_name (self, #(#payload_parameters),* ) -> #sylvia ::cw_std::StdResult< #sylvia ::cw_std::SubMsg<CustomMsgT>>;
+        }
+    }
+
+    /// Emits match arm for [ReplyOn::Success].
+    /// In case neither [ReplyOn::Success] nor [ReplyOn::Always] is present, `Response::events`
+    /// and `Response::data` are forwarded in the `Response`
+    fn emit_success_match_arm(&self, contract_turbofish: &Type) -> TokenStream {
+        let sylvia = crate_module();
+
+        match self
+            .handlers
+            .iter()
+            .find(|(_, reply_on)| reply_on == &ReplyOn::Success || reply_on == &ReplyOn::Always)
+        {
+            Some((method_name, reply_on)) if reply_on == &ReplyOn::Success => {
+                let payload_values = self.payload.iter().map(|field| field.name());
+                let payload_deserialization = self.payload.emit_payload_deserialization();
+                let data_deserialization = self.data.map(DataField::emit_data_deserialization);
+                let data = self.data.map(|_| quote! { data, });
+
+                quote! {
+                    #sylvia ::cw_std::SubMsgResult::Ok(sub_msg_resp) => {
+                        #[allow(deprecated)]
+                        let #sylvia ::cw_std::SubMsgResponse { events, data, msg_responses} = sub_msg_resp;
+                        #payload_deserialization
+                        #data_deserialization
+
+                        #contract_turbofish ::new(). #method_name ((deps, env, gas_used, events, msg_responses).into(), #data #(#payload_values),* )
+                    }
+                }
+            }
+            Some((method_name, reply_on)) if reply_on == &ReplyOn::Always => {
+                let payload_values = self.payload.iter().map(|field| field.name());
+                let payload_deserialization = self.payload.emit_payload_deserialization();
+
+                quote! {
+                    #sylvia ::cw_std::SubMsgResult::Ok(_) => {
+                        #payload_deserialization
+
+                        #contract_turbofish ::new(). #method_name ((deps, env, gas_used, vec![], vec![]).into(), result, #(#payload_values),* )
+                    }
+                }
+            }
+            _ => quote! {
+                #sylvia ::cw_std::SubMsgResult::Ok(sub_msg_resp) => {
+                    let mut resp = sylvia::cw_std::Response::new().add_events(sub_msg_resp.events);
+
+                    #[allow(deprecated)]
+                    if sub_msg_resp.data.is_some() {
+                        resp = resp.set_data(sub_msg_resp.data.unwrap());
+                    }
+
+                    Ok(resp)
+                }
+            },
+        }
+    }
+
+    /// Emits match arm for [ReplyOn::Error].
+    /// In case neither [ReplyOn::Error] nor [ReplyOn::Always] is present,
+    /// the error is forwarded.
+    fn emit_error_match_arm(&self, contract_turbofish: &Type) -> TokenStream {
+        let sylvia = crate_module();
+
+        match self
+            .handlers
+            .iter()
+            .find(|(_, reply_on)| reply_on == &ReplyOn::Error || reply_on == &ReplyOn::Always)
+        {
+            Some((method_name, reply_on)) if reply_on == &ReplyOn::Error => {
+                let payload_values = self.payload.iter().map(|field| field.name());
+                let payload_deserialization = self.payload.emit_payload_deserialization();
+
+                quote! {
+                    #sylvia ::cw_std::SubMsgResult::Err(error) => {
+                        #payload_deserialization
+
+                        #contract_turbofish ::new(). #method_name ((deps, env, gas_used, vec![], vec![]).into(), error, #(#payload_values),* )
+                    }
+                }
+            }
+            Some((method_name, reply_on)) if reply_on == &ReplyOn::Always => {
+                let payload_values = self.payload.iter().map(|field| field.name());
+                let payload_deserialization = self.payload.emit_payload_deserialization();
+
+                quote! {
+                    #sylvia ::cw_std::SubMsgResult::Err(_) => {
+                        #payload_deserialization
+
+                        #contract_turbofish ::new(). #method_name ((deps, env, gas_used, vec![], vec![]).into(), result, #(#payload_values),* )
+                    }
+                }
+            }
+            _ => quote! {
+                #sylvia ::cw_std::SubMsgResult::Err(error) => {
+                    Err(sylvia::cw_std::StdError::generic_err(error)).map_err(Into::into)
+                }
+            },
+        }
+    }
+}
+
+trait ReplyVariant<'a> {
+    fn as_variant_handlers_pair(&'a self) -> Vec<(&'a MsgVariant<'a>, &'a Ident)>;
+    fn as_data_field(&'a self) -> Option<&'a MsgField<'a>>;
+    fn validate_fields_attributes(&'a self);
+}
+
+impl<'a> ReplyVariant<'a> for MsgVariant<'a> {
+    fn as_variant_handlers_pair(&'a self) -> Vec<(&'a MsgVariant<'a>, &'a Ident)> {
+        let variant_handler_id_pair: Vec<_> = self
+            .msg_attr()
+            .handlers()
+            .iter()
+            .map(|handler| (self, handler))
+            .collect();
+
+        if variant_handler_id_pair.is_empty() {
+            return vec![(self, self.function_name())];
+        }
+
+        variant_handler_id_pair
+    }
+
+    /// Validates attributes and returns `Some(MsgField)` if a field marked with `sv::data` attribute
+    /// is present and the `reply_on` attribute is set to `ReplyOn::Success`.
+    fn as_data_field(&'a self) -> Option<&'a MsgField<'a>> {
+        let data_param = self.fields().iter().enumerate().find(|(_, field)| {
+            ParsedSylviaAttributes::new(field.attrs().iter())
+                .data
+                .is_some()
+        });
+        match data_param {
+            Some((index, field))
+                if self.msg_attr().reply_on() == ReplyOn::Success && index == 0 =>
+            {
+                Some(field)
+            }
+            Some((index, field))
+                if self.msg_attr().reply_on() == ReplyOn::Success && index != 0 =>
+            {
+                emit_error!(field.name().span(), "Wrong usage of `#[sv::data]` attribute.";
+                    note = "The `#[sv::data]` attribute can only be used on the first parameter after the `ReplyCtx`."
+                );
+                None
+            }
+            Some((_, field)) if self.msg_attr().reply_on() != ReplyOn::Success => {
+                emit_error!(field.name().span(), "Wrong usage of `#[sv::data]` attribute.";
+                    note = "The `#[sv::data]` attribute can only be used in `success` scenario.";
+                    note = format!("Found usage in `{}` scenario.", self.msg_attr().reply_on())
+                );
+                None
+            }
+            _ => None,
+        }
+    }
+
+    /// Validates if the fields attributes are correct.
+    fn validate_fields_attributes(&'a self) {
+        let field_attrs = self.fields().iter().flat_map(|field| field.attrs());
+        ParsedSylviaAttributes::new(field_attrs);
+    }
+}
+
+pub trait DataField {
+    fn emit_data_deserialization(&self) -> TokenStream;
+}
+
+impl DataField for MsgField<'_> {
+    fn emit_data_deserialization(&self) -> TokenStream {
+        let sylvia = crate_module();
+        let data = ParsedSylviaAttributes::new(self.attrs().iter()).data;
+        let missing_data_err = "Missing reply data field.";
+        let transaction_id = quote! {
+            env
+                .transaction
+                .as_ref()
+                .map(|tx| format!("{}", &tx.index))
+                .unwrap_or_else(|| "Missing".to_string())
+        };
+        let invalid_reply_data_err = quote! {
+            format! {"Invalid reply data at block height: {}, transaction id: {}.\nSerde error while deserializing {}",
+                env.block.height,
+                #transaction_id,
+                err}
+        };
+        let execute_data_deserialization = quote! {
+            let deserialized_data =
+                #sylvia ::cw_utils::parse_execute_response_data(data.as_slice())
+                    .map_err(|err| #sylvia ::cw_std::StdError::generic_err(
+                        format!("Failed deserializing protobuf data: {}", err)
+                    ))?;
+            let deserialized_data = match deserialized_data.data {
+                Some(data) => #sylvia ::cw_std::from_json(&data).map_err(|err| #sylvia ::cw_std::StdError::generic_err( #invalid_reply_data_err ))?,
+                None => return Err(Into::into( #sylvia ::cw_std::StdError::generic_err( #missing_data_err ))),
+            };
+        };
+
+        let instantiate_data_deserialization = quote! {
+            let deserialized_data =
+                #sylvia ::cw_utils::parse_instantiate_response_data(data.as_slice())
+                    .map_err(|err| #sylvia ::cw_std::StdError::generic_err(
+                        format!("Failed deserializing protobuf data: {}", err)
+                    ))?;
+        };
+
+        match data {
+            Some(data) if data.raw && data.opt => quote! {},
+            Some(data) if data.raw => quote! {
+                let data = match data {
+                    Some(data) => data,
+                    None => return Err(Into::into( #sylvia ::cw_std::StdError::generic_err( #missing_data_err ))),
+                };
+            },
+            Some(data) if data.instantiate && data.opt => quote! {
+                let data = match data {
+                    Some(data) => {
+                        #instantiate_data_deserialization
+
+                        Some(deserialized_data)
+                    },
+                    None => None,
+                };
+            },
+            Some(data) if data.instantiate => quote! {
+                let data = match data {
+                    Some(data) => {
+                        #instantiate_data_deserialization
+
+                        deserialized_data
+                    },
+                    None => return Err(Into::into( #sylvia ::cw_std::StdError::generic_err( #missing_data_err ))),
+                };
+            },
+            Some(data) if data.opt => quote! {
+                let data = match data {
+                    Some(data) => {
+                        #execute_data_deserialization
+
+                        Some(deserialized_data)
+                    },
+                    None => None,
+                };
+            },
+            _ => quote! {
+                let data = match data {
+                    Some(data) => {
+                        #execute_data_deserialization
+
+                        deserialized_data
+                    },
+                    None => return Err(Into::into( #sylvia ::cw_std::StdError::generic_err( #missing_data_err ))),
+                };
+            },
+        }
+    }
+}
+
+pub trait PayloadFields {
+    fn emit_payload_deserialization(&self) -> TokenStream;
+    fn emit_payload_serialization(&self) -> TokenStream;
+    fn is_payload_marked(&self) -> bool;
+}
+
+impl PayloadFields for Vec<&MsgField<'_>> {
+    fn emit_payload_deserialization(&self) -> TokenStream {
+        let sylvia = crate_module();
+        if self.is_payload_marked() {
+            // Safe to unwrap as we check if the payload exist.
+            let payload_value = self.first().unwrap().name();
+            return quote! {
+                let #payload_value = payload ;
+            };
+        }
+
+        let deserialized_payload_names = self.iter().map(|field| field.name());
+        quote! {
+            let ( #(#deserialized_payload_names),* ) = #sylvia ::cw_std::from_json(&payload)?;
+        }
+    }
+
+    fn emit_payload_serialization(&self) -> TokenStream {
+        let sylvia = crate_module();
+        if self.is_payload_marked() {
+            // Safe to unwrap as we check if the payload exist.
+            let payload_value = self.first().unwrap().name();
+            return quote! {
+                let payload = #payload_value ;
+            };
+        }
+
+        let payload_values = self.iter().map(|field| field.name());
+        quote! {
+            let payload = #sylvia ::cw_std::to_json_binary(&( #(#payload_values),* ))?;
+        }
+    }
+
+    fn is_payload_marked(&self) -> bool {
+        self.iter().any(|field| {
+            ParsedSylviaAttributes::new(field.attrs().iter())
+                .payload
+                .is_some()
+        })
+    }
+}
+
+/// Maps self to an [Ident] reply id.
+trait AsReplyId {
+    fn as_reply_id(&self) -> Ident;
+}
+
+impl AsReplyId for Ident {
+    fn as_reply_id(&self) -> Ident {
+        let reply_id = format! {"{}_REPLY_ID", self.to_string().to_case(Case::UpperSnake)};
+        Ident::new(&reply_id, self.span())
+    }
+}
diff --git a/sylvia-derive/src/contract/communication/struct_msg.rs b/sylvia-derive/src/contract/communication/struct_msg.rs
index d28834c..b8122b0 100644
--- a/sylvia-derive/src/contract/communication/struct_msg.rs
+++ b/sylvia-derive/src/contract/communication/struct_msg.rs
@@ -96,20 +96,24 @@ impl<'a> StructMessage<'a> {
         let bracketed_unused_generics = emit_bracketed_generics(unused_generics);
 
         let ret_type = variant
+            .msg_attr()
             .msg_type()
             .emit_result_type(&custom.msg_or_default(), &error.error);
-        let name = variant.msg_type().emit_msg_name();
+        let name = variant.msg_attr().msg_type().emit_msg_name();
         let function_name = variant.function_name();
-        let mut msg_name = variant.msg_type().emit_msg_name();
+        let mut msg_name = variant.msg_attr().msg_type().emit_msg_name();
         msg_name.set_span(function_name.span());
 
-        let ctx_type = variant.msg_type().emit_ctx_type(&custom.query_or_default());
+        let ctx_type = variant
+            .msg_attr()
+            .msg_type()
+            .emit_ctx_type(&custom.query_or_default());
         let fields_names: Vec<_> = variant.fields().iter().map(MsgField::name).collect();
         let parameters = variant.fields().iter().map(MsgField::emit_method_field);
         let fields = variant.fields().iter().map(MsgField::emit_pub);
 
         let msg_attrs_to_forward = msg_attrs_to_forward.iter().map(|attr| &attr.attrs);
-        let derive_call = variant.msg_type().emit_derive_call();
+        let derive_call = variant.msg_attr().msg_type().emit_derive_call();
 
         quote! {
             #[allow(clippy::derive_partial_eq_without_eq)]
diff --git a/sylvia-derive/src/contract/communication/wrapper_msg.rs b/sylvia-derive/src/contract/communication/wrapper_msg.rs
index 8d74a31..0fc8896 100644
--- a/sylvia-derive/src/contract/communication/wrapper_msg.rs
+++ b/sylvia-derive/src/contract/communication/wrapper_msg.rs
@@ -214,7 +214,7 @@ impl<'a> GlueMessage<'a> {
                     let msgs: [&[&str]; #variants_cnt] = [#(#messages_call),*];
                     let mut err_msg = msgs.into_iter().flatten().fold(
                         // It might be better to forward the error or serialization, but we just
-                        // deserialized it from JSON, not reason to expect failure here.
+                        // deserialized it from JSON, not reason to expect error here.
                         format!(
                             "Unsupported message received: {}. Messages supported by this contract: ",
                             #sylvia ::serde_json::to_string(&val).unwrap_or_else(|_| String::new())
diff --git a/sylvia-derive/src/contract/mt.rs b/sylvia-derive/src/contract/mt.rs
index 4616146..fa8e2b9 100644
--- a/sylvia-derive/src/contract/mt.rs
+++ b/sylvia-derive/src/contract/mt.rs
@@ -4,23 +4,14 @@ use quote::quote;
 use syn::{parse_quote, GenericParam, ItemImpl, Type};
 
 use crate::crate_module;
+use crate::parser::attributes::features::SylviaFeatures;
 use crate::parser::attributes::msg::MsgType;
 use crate::parser::variant_descs::AsVariantDescs;
 use crate::parser::{
     Custom, FilteredOverrideEntryPoints, OverrideEntryPoint, ParsedSylviaAttributes,
 };
 use crate::types::msg_variant::{MsgVariant, MsgVariants};
-use crate::utils::{emit_bracketed_generics, SvCasing};
-
-fn get_ident_from_type(contract_name: &Type) -> &Ident {
-    let Type::Path(type_path) = contract_name else {
-        unreachable!()
-    };
-    let segments = &type_path.path.segments;
-    assert!(!segments.is_empty());
-    let segment = &segments.last().unwrap();
-    &segment.ident
-}
+use crate::utils::{emit_bracketed_generics, get_ident_from_type, SvCasing};
 
 /// Emits helpers for testing contract messages using MultiTest.
 ///
@@ -33,6 +24,7 @@ pub struct MtHelpers<'a> {
     where_clause: &'a Option<syn::WhereClause>,
     custom: &'a Custom,
     override_entry_points: Vec<OverrideEntryPoint>,
+    sv_features: SylviaFeatures,
     instantiate_variant: MsgVariants<'a, GenericParam>,
     exec_variants: MsgVariants<'a, GenericParam>,
     query_variants: MsgVariants<'a, GenericParam>,
@@ -86,11 +78,10 @@ impl<'a> MtHelpers<'a> {
             where_clause,
         );
 
-        let error_type = ParsedSylviaAttributes::new(source.attrs.iter())
-            .error_attrs
-            .unwrap_or_default()
-            .error;
+        let parsed_attrs = ParsedSylviaAttributes::new(source.attrs.iter());
+        let error_type = parsed_attrs.error_attrs.unwrap_or_default().error;
         let error_type = parse_quote! { #error_type };
+        let sv_features = parsed_attrs.sv_features;
 
         let contract_name = &source.self_ty;
 
@@ -102,6 +93,7 @@ impl<'a> MtHelpers<'a> {
             contract_name,
             custom,
             override_entry_points,
+            sv_features,
             instantiate_variant,
             exec_variants,
             query_variants,
@@ -488,6 +480,7 @@ impl<'a> MtHelpers<'a> {
             contract_name,
             custom,
             override_entry_points,
+            sv_features,
             generic_params,
             migrate_variants,
             reply_variants,
@@ -531,10 +524,24 @@ impl<'a> MtHelpers<'a> {
             None => reply_variants
                 .get_only_variant()
                 .as_ref()
-                .map(|reply| {
-                    let reply_name = reply.name().to_case(Case::Snake);
-                    quote! {
-                        self. #reply_name ((deps, env).into(), msg).map_err(Into::into)
+                .map(|_reply| {
+                    let contract_ident = get_ident_from_type(contract_name);
+                    let contract_turbofish = if !generic_params.is_empty() {
+                        quote! { #contract_ident ::< #(#generic_params,)* > }
+                    } else {
+                        quote! { #contract_ident }
+                    };
+
+                    if sv_features.replies {
+                        quote! {
+                            let contract = #contract_turbofish ::new();
+                            dispatch_reply(deps, env, msg, contract).map_err(Into::into)
+                        }
+                    } else {
+                        let reply_name = _reply.name().to_case(Case::Snake);
+                        quote! {
+                            self. #reply_name ((deps, env).into(), msg).map_err(Into::into)
+                        }
                     }
                 })
                 .unwrap_or_else(|| {
@@ -659,10 +666,10 @@ impl EmitMethods for MsgVariant<'_> {
             .map(|field| field.emit_method_field_folded())
             .collect();
         let arguments = self.as_fields_names();
-        let type_name = self.msg_type().as_accessor_name();
+        let type_name = self.msg_attr().msg_type().as_accessor_name();
         let name = name.to_case(Case::Snake);
 
-        match self.msg_type() {
+        match self.msg_attr().msg_type() {
             MsgType::Exec => quote! {
                 #[track_caller]
                 fn #name (&self, #(#params,)* ) -> #sylvia ::multitest::ExecProxy::< #error_type, #api :: #type_name, #mt_app, #custom_msg> {
@@ -721,10 +728,10 @@ impl EmitMethods for MsgVariant<'_> {
             .iter()
             .map(|field| field.emit_method_field_folded())
             .collect();
-        let type_name = self.msg_type().as_accessor_name();
+        let type_name = self.msg_attr().msg_type().as_accessor_name();
         let name = name.to_case(Case::Snake);
 
-        match self.msg_type() {
+        match self.msg_attr().msg_type() {
             MsgType::Exec => quote! {
                 fn #name (&self, #(#params,)* ) -> #sylvia ::multitest::ExecProxy::< #error_type, #api:: #type_name, MtApp, #custom_msg>;
             },
diff --git a/sylvia-derive/src/entry_points.rs b/sylvia-derive/src/entry_points.rs
index 33544c5..96c5a6a 100644
--- a/sylvia-derive/src/entry_points.rs
+++ b/sylvia-derive/src/entry_points.rs
@@ -6,6 +6,7 @@ use syn::{parse_quote, GenericParam, Ident, ItemImpl, Type, WhereClause};
 
 use crate::crate_module;
 use crate::fold::StripGenerics;
+use crate::parser::attributes::features::SylviaFeatures;
 use crate::parser::attributes::msg::MsgType;
 use crate::parser::variant_descs::AsVariantDescs;
 use crate::parser::{
@@ -68,6 +69,7 @@ pub struct EntryPoints<'a> {
     generics: Vec<&'a GenericParam>,
     where_clause: &'a Option<WhereClause>,
     attrs: &'a EntryPointArgs,
+    sv_features: SylviaFeatures,
 }
 
 impl<'a> EntryPoints<'a> {
@@ -75,6 +77,7 @@ impl<'a> EntryPoints<'a> {
         let name = StripGenerics.fold_type(*source.self_ty.clone());
         let parsed_attrs = ParsedSylviaAttributes::new(source.attrs.iter());
         let override_entry_points = parsed_attrs.override_entry_point_attrs;
+        let sv_features = parsed_attrs.sv_features;
 
         let error = parsed_attrs.error_attrs.unwrap_or_default().error;
 
@@ -96,6 +99,7 @@ impl<'a> EntryPoints<'a> {
             generics,
             where_clause,
             attrs,
+            sv_features,
         }
     }
 
@@ -172,12 +176,13 @@ impl<'a> EntryPoints<'a> {
             error,
             attrs,
             reply,
+            sv_features,
             ..
         } = self;
         let sylvia = crate_module();
 
         let attr_generics = &attrs.generics;
-        let (contract, contract_turbo) = if attr_generics.is_empty() {
+        let (contract, contract_turbofish) = if attr_generics.is_empty() {
             (quote! { #name }, quote! { #name })
         } else {
             (
@@ -201,11 +206,15 @@ impl<'a> EntryPoints<'a> {
             _ => quote! { msg: < #contract as #sylvia ::types::ContractApi> :: #associated_name },
         };
         let dispatch = match msg_ty {
+            MsgType::Reply if sv_features.replies => quote! {
+                let contract = #contract_turbofish ::new();
+                sv::dispatch_reply(deps, env, msg, contract).map_err(Into::into)
+            },
             MsgType::Reply => quote! {
-                #contract_turbo ::new(). #reply((deps, env).into(), msg).map_err(Into::into)
+                #contract_turbofish ::new(). #reply((deps, env).into(), msg).map_err(Into::into)
             },
             _ => quote! {
-                msg.dispatch(& #contract_turbo ::new() , ( #values )).map_err(Into::into)
+                msg.dispatch(& #contract_turbofish ::new() , ( #values )).map_err(Into::into)
             },
         };
 
diff --git a/sylvia-derive/src/interface/mt.rs b/sylvia-derive/src/interface/mt.rs
index ad5aaf4..0fb4f34 100644
--- a/sylvia-derive/src/interface/mt.rs
+++ b/sylvia-derive/src/interface/mt.rs
@@ -199,10 +199,10 @@ impl EmitMethods for MsgVariant<'_> {
             .map(|field| field.emit_method_field_folded())
             .collect();
         let arguments = self.as_fields_names();
-        let type_name = self.msg_type().as_accessor_name();
+        let type_name = self.msg_attr().msg_type().as_accessor_name();
         let name = name.to_case(Case::Snake);
 
-        match self.msg_type() {
+        match self.msg_attr().msg_type() {
             MsgType::Exec => quote! {
                 #[track_caller]
                 fn #name (&self, #(#params,)* ) -> #sylvia ::multitest::ExecProxy::< #error_type, #api :: #type_name, #mt_app, #custom_msg> {
@@ -261,10 +261,10 @@ impl EmitMethods for MsgVariant<'_> {
             .iter()
             .map(|field| field.emit_method_field_folded())
             .collect();
-        let type_name = self.msg_type().as_accessor_name();
+        let type_name = self.msg_attr().msg_type().as_accessor_name();
         let name = name.to_case(Case::Snake);
 
-        match self.msg_type() {
+        match self.msg_attr().msg_type() {
             MsgType::Exec => quote! {
                 fn #name (&self, #(#params,)* ) -> #sylvia ::multitest::ExecProxy::< #error_type, #api:: #type_name, MtApp, #custom_msg>;
             },
diff --git a/sylvia-derive/src/lib.rs b/sylvia-derive/src/lib.rs
index f581f8a..8f708ff 100644
--- a/sylvia-derive/src/lib.rs
+++ b/sylvia-derive/src/lib.rs
@@ -49,7 +49,7 @@ pub(crate) fn crate_module() -> Path {
 /// ```rust
 /// # use sylvia::cw_schema::cw_serde;
 /// # use sylvia::cw_std::{Response, StdError};
-/// # use sylvia::types::{ExecCtx, QueryCtx, SudoCtx};
+/// # use sylvia::ctx::{ExecCtx, QueryCtx, SudoCtx};
 /// #
 /// # #[cw_serde]
 /// # pub struct AdminQueryResponse;
@@ -75,7 +75,7 @@ pub(crate) fn crate_module() -> Path {
 /// ```rust
 /// # use sylvia::cw_schema::cw_serde;
 /// # use sylvia::cw_std::{Response, StdError, DepsMut, Env, MessageInfo};
-/// # use sylvia::types::ExecCtx;
+/// # use sylvia::ctx::ExecCtx;
 /// #
 /// # pub trait SvInterface {
 /// #    type Error: From<StdError>;
@@ -124,7 +124,7 @@ pub(crate) fn crate_module() -> Path {
 /// ```rust
 /// # use sylvia::cw_schema::cw_serde;
 /// # use sylvia::cw_std::{CustomMsg, CustomQuery, Response, StdError};
-/// # use sylvia::types::ExecCtx;
+/// # use sylvia::ctx::ExecCtx;
 /// #
 /// ##[sylvia::interface]
 /// pub trait SvInterface {
@@ -164,7 +164,7 @@ pub(crate) fn crate_module() -> Path {
 /// ```rust
 /// # use sylvia::cw_schema::cw_serde;
 /// # use sylvia::cw_std::{Response, StdError};
-/// # use sylvia::types::{ExecCtx, QueryCtx, SudoCtx};
+/// # use sylvia::ctx::{ExecCtx, QueryCtx, SudoCtx};
 /// #
 /// # #[cw_serde]
 /// # pub struct AdminQueryResponse;
@@ -196,7 +196,7 @@ pub(crate) fn crate_module() -> Path {
 /// ```rust
 /// # use sylvia::cw_schema::cw_serde;
 /// # use sylvia::cw_std::{CustomMsg, CustomQuery, Response, StdError};
-/// # use sylvia::types::{ExecCtx, QueryCtx, SudoCtx};
+/// # use sylvia::ctx::{ExecCtx, QueryCtx, SudoCtx};
 /// #
 /// ##[cw_serde]
 /// pub enum SvCustomMsg {}
@@ -233,7 +233,6 @@ pub(crate) fn crate_module() -> Path {
 /// generated message type. It can be used along with `sv::msg(...)`
 /// and only for message types variants that resolves in an enum field,
 /// i.e. `exec`, `query` and `sudo`.
-///
 #[proc_macro_error]
 #[proc_macro_attribute]
 pub fn interface(attr: TokenStream, item: TokenStream) -> TokenStream {
@@ -263,8 +262,9 @@ fn interface_impl(_attr: TokenStream2, item: TokenStream2) -> TokenStream2 {
 ///
 /// ## Example usage
 /// ```rust
-/// # use sylvia::types::{ExecCtx, InstantiateCtx, MigrateCtx, QueryCtx, ReplyCtx, SudoCtx};
-/// # use sylvia::cw_std::{Response, StdError, Reply};
+/// # use sylvia::ctx::{ExecCtx, InstantiateCtx, MigrateCtx, QueryCtx, SudoCtx};
+/// # use sylvia::ctx::ReplyCtx;
+/// # use sylvia::cw_std::{Binary, Response, StdError, SubMsgResult};
 /// # use cw_storage_plus::Item;
 /// # use thiserror::Error;
 /// #
@@ -282,6 +282,7 @@ fn interface_impl(_attr: TokenStream2, item: TokenStream2) -> TokenStream2 {
 ///
 /// ##[sylvia::contract]
 /// ##[sv::error(ContractError)]
+/// ##[sv::features(replies)]
 /// impl SvContract {
 ///     pub const fn new() -> Self {
 ///         Self {
@@ -310,7 +311,7 @@ fn interface_impl(_attr: TokenStream2, item: TokenStream2) -> TokenStream2 {
 ///     }
 ///
 ///     #[sv::msg(reply)]
-///     fn reply(&self, ctx: ReplyCtx, reply: Reply) -> Result<Response, ContractError> {
+///     fn reply(&self, ctx: ReplyCtx, result: SubMsgResult, #[sv::payload(raw)] payload: Binary) -> Result<Response, ContractError> {
 /// #        Ok(Response::new())
 ///     }
 ///
@@ -325,8 +326,9 @@ fn interface_impl(_attr: TokenStream2, item: TokenStream2) -> TokenStream2 {
 /// This would generate output like:
 ///
 /// ```rust
-/// # use sylvia::types::{ExecCtx, InstantiateCtx, MigrateCtx, QueryCtx, ReplyCtx, SudoCtx};
-/// # use sylvia::cw_std::{Response, StdError};
+/// # use sylvia::ctx::{ExecCtx, InstantiateCtx, MigrateCtx, QueryCtx, SudoCtx};
+/// # use sylvia::ctx::ReplyCtx;
+/// # use sylvia::cw_std::{Binary, Response, StdError, SubMsgResult};
 /// # use cw_storage_plus::Item;
 /// # use thiserror::Error;
 /// #
@@ -435,7 +437,7 @@ fn interface_impl(_attr: TokenStream2, item: TokenStream2) -> TokenStream2 {
 /// implement `QueryResponses`.
 ///
 /// ```rust
-/// # use sylvia::types::{InstantiateCtx, QueryCtx};
+/// # use sylvia::ctx::{InstantiateCtx, QueryCtx};
 /// # use sylvia::cw_std::{Response, StdError};
 /// # use sylvia::cw_schema::cw_serde;
 /// # use cw_storage_plus::Item;
@@ -495,7 +497,7 @@ fn interface_impl(_attr: TokenStream2, item: TokenStream2) -> TokenStream2 {
 /// Specified entry point will be used in generated `multitest` helpers
 ///
 /// ```rust
-/// # use sylvia::types::InstantiateCtx;
+/// # use sylvia::ctx::InstantiateCtx;
 /// # use sylvia::cw_std::{Response, StdResult, MessageInfo, DepsMut, Env, entry_point};
 /// # use sylvia::cw_schema::cw_serde;
 /// #
@@ -535,7 +537,7 @@ fn interface_impl(_attr: TokenStream2, item: TokenStream2) -> TokenStream2 {
 /// Required for the contract to be able to handle an interface message.
 ///
 /// ```rust
-/// # use sylvia::types::InstantiateCtx;
+/// # use sylvia::ctx::InstantiateCtx;
 /// # use sylvia::cw_std::{Response, StdError, StdResult};
 /// # use cw_storage_plus::Item;
 /// #
@@ -573,7 +575,7 @@ fn interface_impl(_attr: TokenStream2, item: TokenStream2) -> TokenStream2 {
 /// you have to pass the name of an interface as below:
 ///
 /// ```rust
-/// # use sylvia::types::InstantiateCtx;
+/// # use sylvia::ctx::InstantiateCtx;
 /// # use sylvia::cw_std::{Response, StdError, StdResult};
 /// # use cw_storage_plus::Item;
 /// #
@@ -621,7 +623,83 @@ fn interface_impl(_attr: TokenStream2, item: TokenStream2) -> TokenStream2 {
 /// and only for message types variants that resolves in an enum field,
 /// i.e. `exec`, `query` and `sudo`.
 ///
+/// ### `sv::features(...)`
+///
+/// Enables additional features for the contract. Allows user to use features that
+/// are considered breaking before the major release.
+///
+/// Currently supported features are:
+/// * `replies` - enables better dispatching of `reply` as well as its auto deserialization.
+///     With this feature enabled, user can use additional parameters in the
+///     `sv::msg` attribute like so:
+///     `#[sv::msg(reply, handlers=[scenario1, scenario2], reply_on=Success)]`.
+///
+///     Based on this parameters reply ids will be generated and associated with
+///     proper scenario specified by the `reply_on` parameter.
+///
+///     User can also specify custom `data` and `payload` types that will be auto
+///     deserialized from the `cosmwasm_std::Binary` type.
+///
+/// ### `sv::payload(raw)`
+///
+/// Requires contract to be marked with the `sv::features(replies)`.
+///
+/// Used next to the reply method argument. It disables auto deserialization
+/// of the payload argument.
+///
+/// ### `sv::data(...)`
+///
+/// Requires contract to be marked with the `sv::features(replies)`.
+///
+/// Used next to the reply method argument. Based on the passed parameters
+/// it enables different behavior:
+///
+/// * `#[sv::data(raw)]` - Returns error if the data is `None`, extracts and forwards
+///     `Binary` if it's `Some`.
+///
+/// * `#[sv::data(raw, opt)]` - Forwards `data` as `Option<Binary>`.
+///
+/// * `#[sv::data(opt)]` - Expects type of `data` in the method signature to be
+///     `data: Option<T> where T: Deserialize`.
+///     Tries to deserialize `data` to type defined in the method signature
+///     and forwards it wrapped in the `Option`.
+///     Requires `data` to be JSON serialized.
+///
+/// If `data` is:
+/// | `Some(valid)` | `Some(invalid)` | `None` |
+/// |---|---|---|
+/// | forwards `Some(valid)` | early returns an error specifying what went wrong with `serde` error attached | `None` - forwards `None` |
+///
+/// * `#[sv::data]` - Expects data in the method signature to be `data: T where T: Deserialize`.
+///     Tries to deserialize data to type defined in the method signature
+///     and forwards it or returns early an error.
+///     Requires `data` to be JSON serialized.
+///
+/// If `data` is:
+/// | `Some(valid)` | `Some(invalid)` | `None` |
+/// |---|---|---|
+/// | forwards `valid` | early returns error specifying what went wrong with `serde` error attached | early returns error specifying the `data` is missing |
+///
+/// * `#[sv::data(instantiate)]` - special case for reply to `WasmMsg::instantiate` and
+///     `WasmMsg::instantiate2`. Tries to deserialize data using
+///     [`parse_instantiate_response_data`](https://docs.rs/cw-utils/latest/cw_utils/fn.parse_instantiate_response_data.html).
+///
+/// If `data` is:
+/// | `Some(valid)` | `Some(invalid)` | `None` |
+/// |---|---|---|
+/// | extracts and forwards `valid` | early returns error specifying what went wrong with `serde` error attached | early returns error specifying the `data` is missing |
+///
+/// * `#[sv::data(instantiate, opt)]` - special case for reply to `WasmMsg::instantiate` and
+///     `WasmMsg::instantiate2`. tries to deserialize data using
+///     [`parse_instantiate_response_data`](https://docs.rs/cw-utils/latest/cw_utils/fn.parse_instantiate_response_data.html).
+///
+/// if `data` is:
+/// | `Some(valid)` | `Some(invalid)` | `None` |
+/// |---|---|---|
+/// | forwards `Some(valid)` | early returns error specifying what went wrong with `serde` error attached | Forwards `None` |
 ///
+/// * Missing `#[sv::data(...)]` - In case `sv::data` is not found Sylvia won't forward the `data` argument
+///     so the `data` should be omited in the method signature.
 #[proc_macro_error]
 #[proc_macro_attribute]
 pub fn contract(attr: TokenStream, item: TokenStream) -> TokenStream {
@@ -654,13 +732,15 @@ fn contract_impl(attr: TokenStream2, item: TokenStream2) -> TokenStream2 {
 ///
 /// ## Example usage
 /// ```rust
-/// # use sylvia::types::{ExecCtx, InstantiateCtx, MigrateCtx, QueryCtx, ReplyCtx, SudoCtx};
-/// # use sylvia::cw_std::{Reply, Response, StdResult};
+/// # use sylvia::ctx::{ExecCtx, InstantiateCtx, MigrateCtx, QueryCtx, SudoCtx};
+/// # use sylvia::ctx::ReplyCtx;
+/// # use sylvia::cw_std::{Binary, Reply, Response, StdResult, SubMsgResult};
 /// #
 /// pub struct SvContract;
 ///
 /// ##[sylvia::entry_points]
 /// ##[sylvia::contract]
+/// ##[sv::features(replies)]
 /// impl SvContract {
 ///     pub const fn new() -> Self {
 ///         Self
@@ -687,7 +767,7 @@ fn contract_impl(attr: TokenStream2, item: TokenStream2) -> TokenStream2 {
 /// #    }
 /// #
 /// #    #[sv::msg(reply)]
-/// #    fn reply(&self, ctx: ReplyCtx, reply: Reply) -> StdResult<Response> {
+/// #    fn reply(&self, ctx: ReplyCtx, result: SubMsgResult, #[sv::payload(raw)] payload: Binary) -> StdResult<Response> {
 /// #        Ok(Response::new())
 /// #    }
 /// #
@@ -706,7 +786,7 @@ fn contract_impl(attr: TokenStream2, item: TokenStream2) -> TokenStream2 {
 /// used in their place in the `entry_points` macro attribute `generics`.
 ///
 /// ```rust
-/// # use sylvia::types::InstantiateCtx;
+/// # use sylvia::ctx::InstantiateCtx;
 /// # use sylvia::cw_std::{Response, StdResult};
 /// # use sylvia::cw_schema::cw_serde;
 /// # use cw_storage_plus::Item;
diff --git a/sylvia-derive/src/parser/attributes/data.rs b/sylvia-derive/src/parser/attributes/data.rs
new file mode 100644
index 0000000..13aefe8
--- /dev/null
+++ b/sylvia-derive/src/parser/attributes/data.rs
@@ -0,0 +1,61 @@
+use proc_macro_error::emit_error;
+use syn::parse::{Parse, ParseStream, Parser};
+use syn::spanned::Spanned;
+use syn::{Error, Ident, MetaList, Result, Token};
+
+/// Type wrapping data parsed from `sv::data` attribute.
+#[derive(Default, Debug)]
+pub struct DataFieldParams {
+    pub raw: bool,
+    pub opt: bool,
+    pub instantiate: bool,
+}
+
+impl DataFieldParams {
+    pub fn new(attr: &MetaList) -> Result<Self> {
+        let data = DataFieldParams::parse
+            .parse2(attr.tokens.clone())
+            .map_err(|err| {
+                emit_error!(err.span(), err);
+                err
+            })?;
+
+        if data.instantiate && data.raw {
+            emit_error!(
+                attr.tokens.span(),
+                "The `instantiate` cannot be used in pair with `raw` parameter.";
+                note = "Use any combination of [`raw`, `opt`] or [`instantiate`, `opt`] pairs."
+            );
+        }
+
+        Ok(data)
+    }
+}
+
+impl Parse for DataFieldParams {
+    fn parse(input: ParseStream) -> Result<Self> {
+        let mut data = Self::default();
+
+        while !input.is_empty() {
+            let option: Ident = input.parse()?;
+            match option.to_string().as_str() {
+                "raw" => data.raw = true,
+                "opt" => data.opt = true,
+                "instantiate" => data.instantiate = true,
+                _ => {
+                    return Err(Error::new(
+                        option.span(),
+                        "Invalid data parameter.\n
+  = note: Expected one of [`raw`, `opt`, `instantiate`] comma separated.\n",
+                    ))
+                }
+            }
+            if !input.peek(Token![,]) {
+                break;
+            }
+            let _: Token![,] = input.parse()?;
+        }
+
+        Ok(data)
+    }
+}
diff --git a/sylvia-derive/src/parser/attributes/features.rs b/sylvia-derive/src/parser/attributes/features.rs
new file mode 100644
index 0000000..185935d
--- /dev/null
+++ b/sylvia-derive/src/parser/attributes/features.rs
@@ -0,0 +1,46 @@
+use proc_macro_error::emit_error;
+use syn::parse::{Parse, ParseStream, Parser};
+use syn::{Error, Ident, MetaList, Result, Token};
+
+/// Type wrapping data parsed from `sv::features` attribute.
+#[derive(Debug, Default)]
+pub struct SylviaFeatures {
+    /// Enables better dispatching and deserialization for replies.
+    pub replies: bool,
+}
+
+impl SylviaFeatures {
+    pub fn new(attr: &MetaList) -> Result<Self> {
+        SylviaFeatures::parse
+            .parse2(attr.tokens.clone())
+            .map_err(|err| {
+                emit_error!(err.span(), err);
+                err
+            })
+    }
+}
+
+impl Parse for SylviaFeatures {
+    fn parse(input: ParseStream) -> Result<Self> {
+        let mut features = Self::default();
+
+        while !input.is_empty() {
+            let feature: Ident = input.parse()?;
+            match feature.to_string().as_str() {
+                "replies" => features.replies = true,
+                _ => {
+                    return Err(Error::new(
+                        feature.span(),
+                        "Invalid feature.\n= note: Supported features for contract macro: [`replies`].\n",
+                    ))
+                }
+            }
+            if !input.peek(Token![,]) {
+                break;
+            }
+            let _: Token![,] = input.parse()?;
+        }
+
+        Ok(features)
+    }
+}
diff --git a/sylvia-derive/src/parser/attributes/mod.rs b/sylvia-derive/src/parser/attributes/mod.rs
index d0d00ff..9d3bedf 100644
--- a/sylvia-derive/src/parser/attributes/mod.rs
+++ b/sylvia-derive/src/parser/attributes/mod.rs
@@ -1,16 +1,22 @@
 //! Module defining parsing of Sylvia attributes.
 //! Every Sylvia attribute should be prefixed with `sv::`
 
+use data::DataFieldParams;
+use features::SylviaFeatures;
+use payload::PayloadFieldParam;
 use proc_macro_error::emit_error;
 use syn::spanned::Spanned;
 use syn::{Attribute, MetaList, PathSegment};
 
 pub mod attr;
 pub mod custom;
+pub mod data;
 pub mod error;
+pub mod features;
 pub mod messages;
 pub mod msg;
 pub mod override_entry_point;
+pub mod payload;
 
 pub use attr::{MsgAttrForwarding, VariantAttrForwarding};
 pub use custom::Custom;
@@ -21,6 +27,7 @@ pub use override_entry_point::{FilteredOverrideEntryPoints, OverrideEntryPoint};
 
 /// This struct represents all possible attributes that
 /// are parsed and utilized by sylvia.
+#[derive(Clone, Copy, Debug, PartialEq)]
 pub enum SylviaAttribute {
     Custom,
     Error,
@@ -29,6 +36,9 @@ pub enum SylviaAttribute {
     OverrideEntryPoint,
     VariantAttrs,
     MsgAttrs,
+    Payload,
+    Data,
+    Features,
 }
 
 impl SylviaAttribute {
@@ -50,6 +60,9 @@ impl SylviaAttribute {
             "override_entry_point" => Some(Self::OverrideEntryPoint),
             "attr" => Some(Self::VariantAttrs),
             "msg_attr" => Some(Self::MsgAttrs),
+            "payload" => Some(Self::Payload),
+            "data" => Some(Self::Data),
+            "features" => Some(Self::Features),
             _ => None,
         }
     }
@@ -66,6 +79,9 @@ pub struct ParsedSylviaAttributes {
     pub override_entry_point_attrs: Vec<OverrideEntryPoint>,
     pub variant_attrs_forward: Vec<VariantAttrForwarding>,
     pub msg_attrs_forward: Vec<MsgAttrForwarding>,
+    pub sv_features: SylviaFeatures,
+    pub data: Option<DataFieldParams>,
+    pub payload: Option<PayloadFieldParam>,
 }
 
 impl ParsedSylviaAttributes {
@@ -77,16 +93,25 @@ impl ParsedSylviaAttributes {
 
             if let (Some(sylvia_attr), Ok(attr)) = (sylvia_attr, &attr_content) {
                 result.match_attribute(&sylvia_attr, attr);
+            } else if sylvia_attr == Some(SylviaAttribute::Data) {
+                // The `sv::data` attribute can be used without parameters.
+                result.data = Some(DataFieldParams::default());
+            } else if sylvia_attr == Some(SylviaAttribute::Payload) {
+                emit_error!(
+                    attr.span(), "Missing parameters for `sv::payload`";
+                    note = "Expected `#[sv::payload(raw)]`"
+                );
             }
         }
 
         if let Some(attr) = result.variant_attrs_forward.first() {
-            if let Some(MsgAttr::Instantiate) = result.msg_attr {
+            let msg_type = result.msg_attr.as_ref().map(MsgAttr::msg_type);
+            if let Some(MsgType::Instantiate) = msg_type {
                 emit_error!(
                     attr.span, "The attribute `sv::attr` is not supported for `instantiate`";
                     note = "Message `instantiate` is a structure, use `#[sv::msg_attr] instead`";
                 );
-            } else if let Some(MsgAttr::Migrate) = result.msg_attr {
+            } else if let Some(MsgType::Migrate) = msg_type {
                 emit_error!(
                     attr.span, "The attribute `sv::attr` is not supported for `migrate`";
                     note = "Message `migrate` is a structure, use `#[sv::msg_attr] instead`";
@@ -157,6 +182,21 @@ impl ParsedSylviaAttributes {
                     self.msg_attrs_forward.push(message_attrs);
                 }
             }
+            SylviaAttribute::Payload => {
+                if let Ok(payload) = PayloadFieldParam::new(attr) {
+                    self.payload = Some(payload);
+                }
+            }
+            SylviaAttribute::Data => {
+                if let Ok(data) = DataFieldParams::new(attr) {
+                    self.data = Some(data);
+                }
+            }
+            SylviaAttribute::Features => {
+                if let Ok(features) = SylviaFeatures::new(attr) {
+                    self.sv_features = features;
+                }
+            }
         }
     }
 }
diff --git a/sylvia-derive/src/parser/attributes/msg.rs b/sylvia-derive/src/parser/attributes/msg.rs
index 25833ec..2a655e2 100644
--- a/sylvia-derive/src/parser/attributes/msg.rs
+++ b/sylvia-derive/src/parser/attributes/msg.rs
@@ -1,8 +1,9 @@
 use proc_macro_error::emit_error;
 use syn::parse::{Error, Parse, ParseStream, Parser};
-use syn::{Ident, MetaList, Result, Token};
+use syn::{bracketed, Ident, MetaList, Result, Token};
 
-/// Type of message to be generated
+/// Supported message types.
+/// Representation of the first parameter in `#[sv::msg(..)] attribute.
 #[derive(PartialEq, Eq, Debug, Clone, Copy)]
 pub enum MsgType {
     Exec,
@@ -16,25 +17,48 @@ pub enum MsgType {
 /// ArgumentParser holds `resp` parameter parsed from `sv::msg` attribute.
 #[derive(Default)]
 struct ArgumentParser {
-    pub resp_type: Option<Ident>,
+    pub query_resp_type: Option<Ident>,
+    pub reply_handlers: Vec<Ident>,
+    pub reply_on: Option<ReplyOn>,
 }
 
 impl Parse for ArgumentParser {
     fn parse(input: ParseStream) -> Result<Self> {
         let mut result = Self::default();
+
         while input.peek2(Ident) {
             let _: Token![,] = input.parse()?;
             let arg_type: Ident = input.parse()?;
-            let _: Token![=] = input.parse()?;
             match arg_type.to_string().as_str() {
                 "resp" => {
+                    let _: Token![=] = input.parse()?;
                     let resp_type: Ident = input.parse()?;
-                    result.resp_type = Some(resp_type);
+                    result.query_resp_type = Some(resp_type);
+                }
+                "handlers" => {
+                    let _: Token![=] = input.parse()?;
+                    let handlers_content;
+                    bracketed!(handlers_content in input);
+
+                    while !handlers_content.is_empty() {
+                        let handler = handlers_content.parse::<Ident>()?;
+                        result.reply_handlers.push(handler);
+                        if !handlers_content.peek(Token![,]) {
+                            break;
+                        }
+                        let _: Token![,] = handlers_content.parse()?;
+                    }
+                }
+                "reply_on" => {
+                    let _: Token![=] = input.parse()?;
+                    let reply_on: Ident = input.parse()?;
+                    let reply_on = ReplyOn::new(reply_on)?;
+                    result.reply_on = Some(reply_on);
                 }
                 _ => {
                     return Err(Error::new(
-                        input.span(),
-                        "Invalid argument type, expected `resp` or no argument.",
+                        arg_type.span(),
+                        "Invalid argument type, expected `resp`, `handlers`, `reply_on` or no argument.",
                     ))
                 }
             }
@@ -43,15 +67,55 @@ impl Parse for ArgumentParser {
     }
 }
 
+/// Representation of `reply_on` parameter in `#[sv::msg(reply(...))]` attribute.
+#[derive(Copy, Debug, Default, Clone, PartialEq)]
+pub enum ReplyOn {
+    Success,
+    Error,
+    #[default]
+    Always,
+}
+
+impl std::fmt::Display for ReplyOn {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            ReplyOn::Success => f.write_str("success"),
+            ReplyOn::Error => f.write_str("error"),
+            ReplyOn::Always => f.write_str("always"),
+        }
+    }
+}
+
+impl ReplyOn {
+    pub fn new(reply_on: Ident) -> Result<Self> {
+        match reply_on.to_string().as_str() {
+            "success" => Ok(Self::Success),
+            "error" => Ok(Self::Error),
+            "always" => Ok(Self::Always),
+            _ => Err(Error::new(
+                reply_on.span(),
+                "Invalid argument type, expected one of `success`, `error` or `always`.",
+            )),
+        }
+    }
+
+    /// Checks if two [ReplyOn] values are unique and if [ReplyOn::Always] is not one of them as it is exclusive
+    /// and cannot be paired with any other value.
+    pub fn excludes(&self, other: &Self) -> bool {
+        let are_equal = self == other;
+        let is_any_always = self == &ReplyOn::Always || other == &ReplyOn::Always;
+
+        are_equal || is_any_always
+    }
+}
+
 /// Parsed representation of `#[sv::msg(...)]` attribute.
-#[derive(Clone)]
-pub enum MsgAttr {
-    Exec,
-    Query { resp_type: Option<Ident> },
-    Instantiate,
-    Migrate,
-    Reply,
-    Sudo,
+#[derive(Debug, Clone)]
+pub struct MsgAttr {
+    msg_type: MsgType,
+    query_resp_type: Option<Ident>,
+    reply_handlers: Vec<Ident>,
+    reply_on: ReplyOn,
 }
 
 impl MsgAttr {
@@ -61,6 +125,22 @@ impl MsgAttr {
             err
         })
     }
+
+    pub fn msg_type(&self) -> MsgType {
+        self.msg_type
+    }
+
+    pub fn resp_type(&self) -> &Option<Ident> {
+        &self.query_resp_type
+    }
+
+    pub fn handlers(&self) -> &[Ident] {
+        &self.reply_handlers
+    }
+
+    pub fn reply_on(&self) -> ReplyOn {
+        self.reply_on
+    }
 }
 
 impl PartialEq<MsgType> for MsgAttr {
@@ -69,36 +149,21 @@ impl PartialEq<MsgType> for MsgAttr {
     }
 }
 
-impl MsgAttr {
-    pub fn msg_type(&self) -> MsgType {
-        match self {
-            Self::Exec { .. } => MsgType::Exec,
-            Self::Query { .. } => MsgType::Query,
-            Self::Instantiate { .. } => MsgType::Instantiate,
-            Self::Migrate { .. } => MsgType::Migrate,
-            Self::Reply { .. } => MsgType::Reply,
-            Self::Sudo { .. } => MsgType::Sudo,
-        }
-    }
-}
-
 impl Parse for MsgAttr {
     fn parse(input: ParseStream) -> Result<Self> {
-        let ty: Ident = input.parse()?;
-        let ArgumentParser { resp_type } = ArgumentParser::parse(input)?;
-
-        let result = match ty.to_string().as_str() {
-            "exec" => Self::Exec,
-            "query" => Self::Query { resp_type },
-            "instantiate" => Self::Instantiate,
-            "migrate" => Self::Migrate,
-            "reply" => Self::Reply,
-            "sudo" => Self::Sudo,
-            _ => return Err(Error::new(
-                input.span(),
-                "Invalid message type, expected one of: `exec`, `query`, `instantiate`, `migrate`, `reply` or `sudo`.",
-            ))
-        };
-        Ok(result)
+        let msg_type: Ident = input.parse()?;
+        let msg_type = MsgType::new(&msg_type)?;
+        let ArgumentParser {
+            query_resp_type,
+            reply_handlers,
+            reply_on,
+        } = ArgumentParser::parse(input)?;
+
+        Ok(Self {
+            msg_type,
+            query_resp_type,
+            reply_handlers,
+            reply_on: reply_on.unwrap_or_default(),
+        })
     }
 }
diff --git a/sylvia-derive/src/parser/attributes/payload.rs b/sylvia-derive/src/parser/attributes/payload.rs
new file mode 100644
index 0000000..1f47951
--- /dev/null
+++ b/sylvia-derive/src/parser/attributes/payload.rs
@@ -0,0 +1,44 @@
+use proc_macro_error::emit_error;
+use syn::parse::{Parse, ParseStream, Parser};
+use syn::{Error, Ident, MetaList, Result};
+
+/// Type wrapping data parsed from `sv::payload` attribute.
+#[derive(Default, Debug)]
+pub struct PayloadFieldParam;
+
+impl PayloadFieldParam {
+    pub fn new(attr: &MetaList) -> Result<Self> {
+        let data = PayloadFieldParam::parse
+            .parse2(attr.tokens.clone())
+            .map_err(|err| {
+                emit_error!(err.span(), err);
+                err
+            })?;
+
+        Ok(data)
+    }
+}
+
+impl Parse for PayloadFieldParam {
+    fn parse(input: ParseStream) -> Result<Self> {
+        let option: Ident = input.parse()?;
+        match option.to_string().as_str() {
+            "raw" => (),
+            _ => {
+                return Err(Error::new(
+                    option.span(),
+                    "Invalid payload parameter.\n= note: Expected [`raw`].\n",
+                ))
+            }
+        };
+
+        if !input.is_empty() {
+            return Err(Error::new(
+                input.span(),
+                "Unexpected tokens inside `sv::payload` attribute.\n= note: Expected parameters: [`raw`] `.\n",
+            ));
+        }
+
+        Ok(Self)
+    }
+}
diff --git a/sylvia-derive/src/parser/mod.rs b/sylvia-derive/src/parser/mod.rs
index 5a8e955..36201c1 100644
--- a/sylvia-derive/src/parser/mod.rs
+++ b/sylvia-derive/src/parser/mod.rs
@@ -67,6 +67,8 @@ pub fn process_fields<'s, Generic>(
 where
     Generic: GetPath + PartialEq,
 {
+    assert_no_self_ctx_attributes(sig);
+
     sig.inputs
         .iter()
         .skip(2)
@@ -80,3 +82,33 @@ where
         })
         .collect()
 }
+
+/// Assert Sylvia attributes were not used on `self` and `ctx` parameters.
+fn assert_no_self_ctx_attributes(sig: &Signature) {
+    sig.inputs.iter().take(2).for_each(|arg| match arg {
+        FnArg::Receiver(item) => {
+            item.attrs.iter().for_each(|attr| {
+                if SylviaAttribute::new(attr).is_none() {
+                    return;
+                }
+                emit_error!(attr.span(),
+                    "Invalid usage of Sylvia attribute.";
+                    note = "First and second arguments of the method should be `self` and `ctx` respectively.";
+                    note = "Unexpected attribute on `self` parameter."
+                );
+            });
+        }
+        FnArg::Typed(item) => {
+            item.attrs.iter().for_each(|attr| {
+                if SylviaAttribute::new(attr).is_none() {
+                    return;
+                }
+                emit_error!(attr.span(),
+                    "Invalid usage of Sylvia attribute.";
+                    note = "First and second arguments of the method should be `self` and `ctx` respectively.";
+                    note = "Unexpected attribute on `ctx` parameter."
+                );
+            });
+        }
+    });
+}
diff --git a/sylvia-derive/src/types/msg_field.rs b/sylvia-derive/src/types/msg_field.rs
index 14f26de..ecaf990 100644
--- a/sylvia-derive/src/types/msg_field.rs
+++ b/sylvia-derive/src/types/msg_field.rs
@@ -115,4 +115,12 @@ impl<'a> MsgField<'a> {
     pub fn name(&self) -> &'a Ident {
         self.name
     }
+
+    pub fn ty(&self) -> &'a Type {
+        self.ty
+    }
+
+    pub fn attrs(&self) -> &'a Vec<Attribute> {
+        self.attrs
+    }
 }
diff --git a/sylvia-derive/src/types/msg_type.rs b/sylvia-derive/src/types/msg_type.rs
index b89edf0..562fe39 100644
--- a/sylvia-derive/src/types/msg_type.rs
+++ b/sylvia-derive/src/types/msg_type.rs
@@ -2,7 +2,8 @@ use proc_macro2::TokenStream;
 use proc_macro_error::emit_error;
 use quote::quote;
 use syn::fold::Fold;
-use syn::{parse_quote, GenericParam, Ident, Type};
+use syn::parse::Error;
+use syn::{parse_quote, GenericParam, Ident, Result, Type};
 
 use crate::crate_module;
 use crate::fold::StripSelfPath;
@@ -10,6 +11,21 @@ use crate::parser::attributes::msg::MsgType;
 use crate::parser::Customs;
 
 impl MsgType {
+    pub fn new(msg_type: &Ident) -> Result<Self> {
+        match msg_type.to_string().as_str() {
+            "exec" => Ok(Self::Exec),
+            "query" => Ok(Self::Query ),
+            "instantiate" => Ok(Self::Instantiate),
+            "migrate" => Ok(Self::Migrate),
+            "reply" => Ok(Self::Reply ),
+            "sudo" => Ok(Self::Sudo),
+            _ => Err(Error::new(
+                msg_type.span(),
+                "Invalid message type, expected one of: `exec`, `query`, `instantiate`, `migrate`, `reply` or `sudo`.",
+            ))
+        }
+    }
+
     pub fn emit_ctx_type(self, query_type: &Type) -> TokenStream {
         use MsgType::*;
 
diff --git a/sylvia-derive/src/types/msg_variant.rs b/sylvia-derive/src/types/msg_variant.rs
index 75f06f4..7edb1a0 100644
--- a/sylvia-derive/src/types/msg_variant.rs
+++ b/sylvia-derive/src/types/msg_variant.rs
@@ -23,7 +23,7 @@ pub struct MsgVariant<'a> {
     /// Type extracted only in case of `Query` and used in `cosmwasm_schema::QueryResponses`
     /// `returns` attribute.
     return_type: Option<Type>,
-    msg_type: MsgType,
+    msg_attr: MsgAttr,
     attrs_to_forward: Vec<VariantAttrForwarding>,
 }
 
@@ -42,9 +42,9 @@ impl<'a> MsgVariant<'a> {
 
         let name = function_name.to_case(Case::UpperCamel);
         let fields = process_fields(sig, generics_checker);
-        let msg_type = msg_attr.msg_type();
 
-        let return_type = if let MsgAttr::Query { resp_type, .. } = msg_attr {
+        let return_type = if msg_attr.msg_type() == MsgType::Query {
+            let resp_type = &msg_attr.resp_type();
             match resp_type {
                 Some(resp_type) => {
                     let resp_type = parse_quote! { #resp_type };
@@ -67,7 +67,7 @@ impl<'a> MsgVariant<'a> {
             function_name,
             fields,
             return_type,
-            msg_type,
+            msg_attr,
             attrs_to_forward,
         }
     }
@@ -77,13 +77,13 @@ impl<'a> MsgVariant<'a> {
         let Self {
             name,
             fields,
-            msg_type,
+            msg_attr,
             return_type,
             attrs_to_forward,
             ..
         } = self;
         let fields = fields.iter().map(MsgField::emit);
-        let returns_attribute = msg_type.emit_returns_attribute(return_type);
+        let returns_attribute = msg_attr.msg_type().emit_returns_attribute(return_type);
         let attrs_to_forward = attrs_to_forward.iter().map(|attr| &attr.attrs);
 
         quote! {
@@ -103,7 +103,7 @@ impl<'a> MsgVariant<'a> {
             name,
             fields,
             function_name,
-            msg_type,
+            msg_attr,
             ..
         } = self;
 
@@ -119,7 +119,7 @@ impl<'a> MsgVariant<'a> {
             .zip(args.clone())
             .map(|(field, num_field)| quote!(#field : #num_field));
 
-        let method_call = msg_type.emit_dispatch_leg(function_name, &args);
+        let method_call = msg_attr.msg_type().emit_dispatch_leg(function_name, &args);
 
         quote! {
             #name {
@@ -166,8 +166,8 @@ impl<'a> MsgVariant<'a> {
         &self.fields
     }
 
-    pub fn msg_type(&self) -> &MsgType {
-        &self.msg_type
+    pub fn msg_attr(&self) -> &MsgAttr {
+        &self.msg_attr
     }
 
     pub fn return_type(&self) -> &Option<Type> {
diff --git a/sylvia-derive/src/utils.rs b/sylvia-derive/src/utils.rs
index adf5c6f..5dff5fe 100644
--- a/sylvia-derive/src/utils.rs
+++ b/sylvia-derive/src/utils.rs
@@ -2,13 +2,15 @@ use convert_case::Casing;
 use proc_macro2::TokenStream;
 use proc_macro_error::emit_error;
 use quote::{quote, ToTokens};
+use syn::fold::Fold;
 use syn::spanned::Spanned;
 use syn::visit::Visit;
 use syn::{
-    parse_quote, GenericArgument, Ident, Path, PathArguments, ReturnType, Type, WhereClause,
-    WherePredicate,
+    parse_quote, GenericArgument, GenericParam, Ident, Path, PathArguments, ReturnType, Type,
+    WhereClause, WherePredicate,
 };
 
+use crate::fold::StripGenerics;
 use crate::parser::check_generics::{CheckGenerics, GetPath};
 
 /// Filters where predicates leaving only ones found in `used_generics`
@@ -87,6 +89,25 @@ pub fn emit_bracketed_generics<GenericT: ToTokens>(unbonded_generics: &[GenericT
     }
 }
 
+pub fn get_ident_from_type(contract_name: &Type) -> &Ident {
+    let Type::Path(type_path) = contract_name else {
+        unreachable!()
+    };
+    let segments = &type_path.path.segments;
+    assert!(!segments.is_empty());
+    let segment = &segments.last().unwrap();
+    &segment.ident
+}
+
+pub fn emit_turbofish(ty: &Type, generics: &[&GenericParam]) -> Type {
+    if !generics.is_empty() {
+        let stripped_ty = StripGenerics.fold_type(ty.clone());
+        parse_quote! { #stripped_ty :: < #(#generics),* > }
+    } else {
+        parse_quote! { #ty }
+    }
+}
+
 /// Trait for converting `Ident` to different cases preserving original [proc_macro2::Span].
 pub trait SvCasing {
     fn to_case(&self, case: convert_case::Case) -> Self;
diff --git a/sylvia/Cargo.toml b/sylvia/Cargo.toml
index 6506844..92df9d7 100644
--- a/sylvia/Cargo.toml
+++ b/sylvia/Cargo.toml
@@ -60,6 +60,7 @@ cw-storage-plus = { workspace = true }
 cw-utils = { workspace = true }
 thiserror = { workspace = true }
 trybuild = "1.0.91"
+itertools = "0.13.0"
 
 [package.metadata.docs.rs]
 all-features = true
diff --git a/sylvia/examples/basic.rs b/sylvia/examples/basic.rs
index 9ccd252..019b087 100644
--- a/sylvia/examples/basic.rs
+++ b/sylvia/examples/basic.rs
@@ -2,7 +2,7 @@ use anyhow::Error;
 use cosmwasm_std::{Addr, Response};
 use cw_storage_plus::{Item, Map};
 use sylvia::contract;
-use sylvia::types::InstantiateCtx;
+use sylvia::ctx::InstantiateCtx;
 
 #[derive(
     sylvia::serde::Serialize,
@@ -33,8 +33,8 @@ pub struct MemberResp {
 
 mod group {
     use cosmwasm_std::{Response, StdError};
+    use sylvia::ctx::{ExecCtx, QueryCtx};
     use sylvia::interface;
-    use sylvia::types::{ExecCtx, QueryCtx};
 
     use crate::{Member, MemberResp};
 
@@ -66,7 +66,7 @@ mod group {
 mod impl_group {
     use anyhow::Error;
     use cosmwasm_std::Response;
-    use sylvia::types::{ExecCtx, QueryCtx};
+    use sylvia::ctx::{ExecCtx, QueryCtx};
 
     use crate::{GroupContract, MemberResp};
 
diff --git a/sylvia/src/builder/instantiate.rs b/sylvia/src/builder/instantiate.rs
new file mode 100644
index 0000000..76115d2
--- /dev/null
+++ b/sylvia/src/builder/instantiate.rs
@@ -0,0 +1,71 @@
+use cosmwasm_std::{Binary, Coin, WasmMsg};
+
+/// Builder for the CW instantiate message.
+pub struct InstantiateBuilder {
+    /// Serialized instantiate message.
+    msg: Binary,
+    /// Id of the contract stored on the chain.
+    code_id: u64,
+    /// Admin address of the contract.
+    admin: Option<String>,
+    /// A human-readable label for the contract.
+    label: Option<String>,
+    /// Funds sent to the contract.
+    funds: Vec<Coin>,
+}
+
+impl InstantiateBuilder {
+    /// Create a new [InstantiateBuilder] with the given message and code id.
+    /// The message should be a serialized instance of the `InstantiateMsg` struct of the contract.
+    pub fn new(msg: Binary, code_id: u64) -> Self {
+        Self {
+            msg,
+            code_id,
+            admin: None,
+            label: None,
+            funds: vec![],
+        }
+    }
+
+    /// Set label for the contract.
+    pub fn with_label(mut self, label: impl Into<String>) -> Self {
+        self.label = Some(label.into());
+        self
+    }
+
+    /// Set admin address for the contract.
+    pub fn with_admin(mut self, admin: String) -> Self {
+        self.admin = Some(admin);
+        self
+    }
+
+    /// Set funds to be sent to the contract.
+    pub fn with_funds(mut self, funds: Vec<Coin>) -> Self {
+        self.funds = funds;
+        self
+    }
+
+    /// Build [WasmMsg::Instantiate] message.
+    pub fn build(self) -> WasmMsg {
+        WasmMsg::Instantiate {
+            code_id: self.code_id,
+            msg: self.msg,
+            admin: self.admin,
+            label: self.label.unwrap_or_default(),
+            funds: self.funds,
+        }
+    }
+
+    #[cfg(feature = "cosmwasm_1_2")]
+    /// Build [WasmMsg::Instantiate2] message.
+    pub fn build2(self, salt: Binary) -> WasmMsg {
+        WasmMsg::Instantiate2 {
+            code_id: self.code_id,
+            msg: self.msg,
+            admin: self.admin,
+            label: self.label.unwrap_or_default(),
+            funds: self.funds,
+            salt,
+        }
+    }
+}
diff --git a/sylvia/src/builder/mod.rs b/sylvia/src/builder/mod.rs
new file mode 100644
index 0000000..fef8bbf
--- /dev/null
+++ b/sylvia/src/builder/mod.rs
@@ -0,0 +1,3 @@
+//! This module contains builder types.
+
+pub mod instantiate;
diff --git a/sylvia/src/ctx.rs b/sylvia/src/ctx.rs
new file mode 100644
index 0000000..7c96d9f
--- /dev/null
+++ b/sylvia/src/ctx.rs
@@ -0,0 +1,133 @@
+//! Module for context types used in method signatures.
+
+use cosmwasm_std::{Deps, DepsMut, Empty, Env, Event, MessageInfo, MsgResponse};
+
+/// Represantation of `reply` context received in entry point.
+#[non_exhaustive]
+pub struct ReplyCtx<'a, C: cosmwasm_std::CustomQuery = Empty> {
+    pub deps: DepsMut<'a, C>,
+    pub env: Env,
+    pub gas_used: u64,
+    pub events: Vec<Event>,
+    pub msg_responses: Vec<MsgResponse>,
+}
+
+/// Represantation of `migrate` context received in entry point.
+#[non_exhaustive]
+pub struct MigrateCtx<'a, C: cosmwasm_std::CustomQuery = Empty> {
+    pub deps: DepsMut<'a, C>,
+    pub env: Env,
+}
+
+/// Represantation of `execute` context received in entry point.
+#[non_exhaustive]
+pub struct ExecCtx<'a, C: cosmwasm_std::CustomQuery = Empty> {
+    pub deps: DepsMut<'a, C>,
+    pub env: Env,
+    pub info: MessageInfo,
+}
+
+/// Represantation of `instantiate` context received in entry point.
+#[non_exhaustive]
+pub struct InstantiateCtx<'a, C: cosmwasm_std::CustomQuery = Empty> {
+    pub deps: DepsMut<'a, C>,
+    pub env: Env,
+    pub info: MessageInfo,
+}
+
+/// Represantation of `query` context received in entry point.
+#[non_exhaustive]
+pub struct QueryCtx<'a, C: cosmwasm_std::CustomQuery = Empty> {
+    pub deps: Deps<'a, C>,
+    pub env: Env,
+}
+
+/// Represantation of `sudo` context received in entry point.
+#[non_exhaustive]
+pub struct SudoCtx<'a, C: cosmwasm_std::CustomQuery = Empty> {
+    pub deps: DepsMut<'a, C>,
+    pub env: Env,
+}
+
+impl<C: cosmwasm_std::CustomQuery> ExecCtx<'_, C> {
+    pub fn branch(&'_ mut self) -> ExecCtx<'_, C> {
+        ExecCtx {
+            deps: self.deps.branch(),
+            env: self.env.clone(),
+            info: self.info.clone(),
+        }
+    }
+}
+
+impl<C: cosmwasm_std::CustomQuery> InstantiateCtx<'_, C> {
+    pub fn branch(&'_ mut self) -> InstantiateCtx<'_, C> {
+        InstantiateCtx {
+            deps: self.deps.branch(),
+            env: self.env.clone(),
+            info: self.info.clone(),
+        }
+    }
+}
+
+impl<C: cosmwasm_std::CustomQuery> SudoCtx<'_, C> {
+    pub fn branch(&'_ mut self) -> SudoCtx<'_, C> {
+        SudoCtx {
+            deps: self.deps.branch(),
+            env: self.env.clone(),
+        }
+    }
+}
+
+impl<'a, C: cosmwasm_std::CustomQuery> From<(DepsMut<'a, C>, Env)> for MigrateCtx<'a, C> {
+    fn from((deps, env): (DepsMut<'a, C>, Env)) -> Self {
+        Self { deps, env }
+    }
+}
+
+impl<'a, C: cosmwasm_std::CustomQuery>
+    From<(DepsMut<'a, C>, Env, u64, Vec<Event>, Vec<MsgResponse>)> for ReplyCtx<'a, C>
+{
+    fn from(
+        (deps, env, gas_used, events, msg_responses): (
+            DepsMut<'a, C>,
+            Env,
+            u64,
+            Vec<Event>,
+            Vec<MsgResponse>,
+        ),
+    ) -> Self {
+        Self {
+            deps,
+            env,
+            gas_used,
+            events,
+            msg_responses,
+        }
+    }
+}
+
+impl<'a, C: cosmwasm_std::CustomQuery> From<(DepsMut<'a, C>, Env, MessageInfo)> for ExecCtx<'a, C> {
+    fn from((deps, env, info): (DepsMut<'a, C>, Env, MessageInfo)) -> Self {
+        Self { deps, env, info }
+    }
+}
+
+impl<'a, C: cosmwasm_std::CustomQuery> From<(DepsMut<'a, C>, Env, MessageInfo)>
+    for InstantiateCtx<'a, C>
+{
+    fn from((deps, env, info): (DepsMut<'a, C>, Env, MessageInfo)) -> Self {
+        Self { deps, env, info }
+    }
+}
+
+impl<'a, C: cosmwasm_std::CustomQuery> From<(Deps<'a, C>, Env)> for QueryCtx<'a, C> {
+    fn from((deps, env): (Deps<'a, C>, Env)) -> Self {
+        Self { deps, env }
+    }
+}
+
+impl<'a, C: cosmwasm_std::CustomQuery> From<(DepsMut<'a, C>, Env)> for SudoCtx<'a, C> {
+    fn from((deps, env): (DepsMut<'a, C>, Env)) -> Self {
+        Self { deps, env }
+    }
+}
diff --git a/sylvia/src/lib.rs b/sylvia/src/lib.rs
index 1945c6f..4b0a1ea 100644
--- a/sylvia/src/lib.rs
+++ b/sylvia/src/lib.rs
@@ -3,6 +3,8 @@
 //!
 //! Most of implementation lies in `sylvia-derive` crate which is reexported here
 
+pub mod builder;
+pub mod ctx;
 pub mod into_response;
 #[cfg_attr(docsrs, doc(cfg(feature = "mt")))]
 #[cfg(feature = "mt")]
diff --git a/sylvia/src/types.rs b/sylvia/src/types.rs
index 0c0e937..3dd4b01 100644
--- a/sylvia/src/types.rs
+++ b/sylvia/src/types.rs
@@ -1,4 +1,5 @@
 //! Module providing utilities to build and use sylvia contracts.
+
 use cosmwasm_std::{Binary, Coin, Deps, DepsMut, Empty, Env, MessageInfo, WasmMsg};
 use derivative::Derivative;
 use schemars::JsonSchema;
@@ -16,7 +17,7 @@ use serde::{Deserialize, Serialize};
 ///
 /// ```rust
 /// mod admin_contract {
-/// #   use sylvia::types::{InstantiateCtx, QueryCtx};
+/// #   use sylvia::ctx::{InstantiateCtx, QueryCtx};
 /// #   use sylvia::cw_std::{Addr, Response, StdResult};
 /// #
 ///     pub struct AdminContract;
@@ -62,7 +63,7 @@ use serde::{Deserialize, Serialize};
 ///
 /// ```rust
 /// mod admin_contract {
-/// #   use sylvia::types::{InstantiateCtx, QueryCtx};
+/// #   use sylvia::ctx::{InstantiateCtx, QueryCtx};
 /// #   use sylvia::cw_std::{Addr, Response, StdResult};
 /// #
 ///     pub struct AdminContract;
@@ -83,7 +84,7 @@ use serde::{Deserialize, Serialize};
 /// }
 ///
 /// mod admin_interface {
-/// #   use sylvia::types::QueryCtx;
+/// #   use sylvia::ctx::QueryCtx;
 /// #   use sylvia::cw_std::{Addr, StdError, StdResult};
 /// #   use crate::admin_contract::AdminContract;
 /// #
@@ -177,7 +178,7 @@ pub struct ReadyExecutorBuilderState;
 /// pub mod another_contract {
 ///     # use cosmwasm_std::{Response, StdResult};
 ///     # use sylvia::contract;
-///     # use sylvia::types::{ExecCtx, InstantiateCtx};
+///     # use sylvia::ctx::{ExecCtx, InstantiateCtx};
 ///     pub struct AnotherContract {}
 ///     
 ///     #[contract]
@@ -198,7 +199,8 @@ pub struct ReadyExecutorBuilderState;
 /// # use cosmwasm_std::{coin, Addr, Response, StdResult};
 /// # use cw_storage_plus::Item;
 /// # use sylvia::contract;
-/// # use sylvia::types::{ExecCtx, InstantiateCtx, Remote};
+/// # use sylvia::ctx::{ExecCtx, InstantiateCtx};
+/// # use sylvia::types::Remote;
 /// # use another_contract::AnotherContract;
 /// # use another_contract::sv::Executor;
 ///
@@ -240,7 +242,7 @@ pub struct ReadyExecutorBuilderState;
 /// pub mod interface {
 ///     # use cosmwasm_std::{Response, StdResult, StdError};
 ///     # use sylvia::interface;
-///     # use sylvia::types::ExecCtx;
+///     # use sylvia::ctx::ExecCtx;
 ///     #[interface]
 ///     pub trait Interface {
 ///         type Error: From<StdError>;
@@ -324,7 +326,7 @@ impl ExecutorBuilder<ReadyExecutorBuilderState> {
 ///
 /// ```rust
 /// mod admin_contract {
-/// #   use sylvia::types::{InstantiateCtx, QueryCtx};
+/// #   use sylvia::ctx::{InstantiateCtx, QueryCtx};
 /// #   use sylvia::cw_std::{Addr, Response, StdResult};
 /// #
 ///     pub struct AdminContract;
@@ -429,9 +431,25 @@ impl<'a, Contract: ?Sized> Remote<'a, Contract> {
         }
     }
 
+    /// Creates a new instance of [ExecutorBuilder] from underlying contract address.
     pub fn executor(&self) -> ExecutorBuilder<(EmptyExecutorBuilderState, Contract)> {
         ExecutorBuilder::<(EmptyExecutorBuilderState, Contract)>::new(&self.addr)
     }
+
+    /// Creates a new instance of [WasmMsg::UpdateAdmin] from underlying contract address and provided admin address.
+    pub fn update_admin(&self, new_admin: &str) -> WasmMsg {
+        WasmMsg::UpdateAdmin {
+            contract_addr: self.addr.to_string(),
+            admin: new_admin.to_string(),
+        }
+    }
+
+    /// Creates a new instance of [WasmMsg::ClearAdmin] from underlying contract address.
+    pub fn clear_admin(&self) -> WasmMsg {
+        WasmMsg::ClearAdmin {
+            contract_addr: self.addr.to_string(),
+        }
+    }
 }
 
 impl<'a, Contract: ?Sized> AsRef<cosmwasm_std::Addr> for Remote<'a, Contract> {
@@ -441,50 +459,70 @@ impl<'a, Contract: ?Sized> AsRef<cosmwasm_std::Addr> for Remote<'a, Contract> {
     }
 }
 
-/// Represantation of `reply` context received in entry point as
-/// (DepsMut, Env) tuple.
+/// Represantation of `reply` context received in entry point.
+#[deprecated(
+    since = "1.3.0",
+    note = "This type will be replaced with `sylvia::ctx::ReplyCtx` in 2.0.0.\n
+        You can use `sylvia::ctx::ReplyCtx` before the 2.0.0 release by using `#[sv::features(replies)]` attribute."
+)]
 pub struct ReplyCtx<'a, C: cosmwasm_std::CustomQuery = Empty> {
     pub deps: DepsMut<'a, C>,
     pub env: Env,
 }
 
-/// Represantation of `reply` context received in entry point as
-/// (DepsMut, Env) tuple.
+/// Represantation of `migrate` context received in entry point.
+#[deprecated(
+    since = "1.3.0",
+    note = "This type will be replaced with `sylvia::ctx::MigrateCtx` in 2.0.0."
+)]
 pub struct MigrateCtx<'a, C: cosmwasm_std::CustomQuery = Empty> {
     pub deps: DepsMut<'a, C>,
     pub env: Env,
 }
 
-/// Represantation of `reply` context received in entry point as
-/// (DepsMut, Env, MessageInfo) tuple.
+/// Represantation of `execute` context received in entry point.
+#[deprecated(
+    since = "1.3.0",
+    note = "This type will be replaced with `sylvia::ctx::ExecCtx` in 2.0.0."
+)]
 pub struct ExecCtx<'a, C: cosmwasm_std::CustomQuery = Empty> {
     pub deps: DepsMut<'a, C>,
     pub env: Env,
     pub info: MessageInfo,
 }
 
-/// Represantation of `instantiate` context received in entry point as
-/// (DepsMut, Env, MessageInfo) tuple.
+/// Represantation of `instantiate` context received in entry point.
+#[deprecated(
+    since = "1.3.0",
+    note = "This type will be replaced with `sylvia::ctx::InstantiateCtx` in 2.0.0."
+)]
 pub struct InstantiateCtx<'a, C: cosmwasm_std::CustomQuery = Empty> {
     pub deps: DepsMut<'a, C>,
     pub env: Env,
     pub info: MessageInfo,
 }
 
-/// Represantation of `query` context received in entry point as
-/// (Deps, Env) tuple.
+/// Represantation of `query` context received in entry point.
+#[deprecated(
+    since = "1.3.0",
+    note = "This type will be replaced with `sylvia::ctx::QueryCtx` in 2.0.0."
+)]
 pub struct QueryCtx<'a, C: cosmwasm_std::CustomQuery = Empty> {
     pub deps: Deps<'a, C>,
     pub env: Env,
 }
 
-/// Represantation of `sudo` context received in entry point as
-/// (DepsMut, Env) tuple.
+/// Represantation of `sudo` context received in entry point.
+#[deprecated(
+    since = "1.3.0",
+    note = "This type will be replaced with `sylvia::ctx::SudoCtx` in 2.0.0."
+)]
 pub struct SudoCtx<'a, C: cosmwasm_std::CustomQuery = Empty> {
     pub deps: DepsMut<'a, C>,
     pub env: Env,
 }
 
+#[allow(deprecated)]
 impl<C: cosmwasm_std::CustomQuery> ExecCtx<'_, C> {
     pub fn branch(&'_ mut self) -> ExecCtx<'_, C> {
         ExecCtx {
@@ -495,6 +533,7 @@ impl<C: cosmwasm_std::CustomQuery> ExecCtx<'_, C> {
     }
 }
 
+#[allow(deprecated)]
 impl<C: cosmwasm_std::CustomQuery> InstantiateCtx<'_, C> {
     pub fn branch(&'_ mut self) -> InstantiateCtx<'_, C> {
         InstantiateCtx {
@@ -505,6 +544,7 @@ impl<C: cosmwasm_std::CustomQuery> InstantiateCtx<'_, C> {
     }
 }
 
+#[allow(deprecated)]
 impl<C: cosmwasm_std::CustomQuery> SudoCtx<'_, C> {
     pub fn branch(&'_ mut self) -> SudoCtx<'_, C> {
         SudoCtx {
@@ -514,24 +554,28 @@ impl<C: cosmwasm_std::CustomQuery> SudoCtx<'_, C> {
     }
 }
 
+#[allow(deprecated)]
 impl<'a, C: cosmwasm_std::CustomQuery> From<(DepsMut<'a, C>, Env)> for MigrateCtx<'a, C> {
     fn from((deps, env): (DepsMut<'a, C>, Env)) -> Self {
         Self { deps, env }
     }
 }
 
+#[allow(deprecated)]
 impl<'a, C: cosmwasm_std::CustomQuery> From<(DepsMut<'a, C>, Env)> for ReplyCtx<'a, C> {
     fn from((deps, env): (DepsMut<'a, C>, Env)) -> Self {
         Self { deps, env }
     }
 }
 
+#[allow(deprecated)]
 impl<'a, C: cosmwasm_std::CustomQuery> From<(DepsMut<'a, C>, Env, MessageInfo)> for ExecCtx<'a, C> {
     fn from((deps, env, info): (DepsMut<'a, C>, Env, MessageInfo)) -> Self {
         Self { deps, env, info }
     }
 }
 
+#[allow(deprecated)]
 impl<'a, C: cosmwasm_std::CustomQuery> From<(DepsMut<'a, C>, Env, MessageInfo)>
     for InstantiateCtx<'a, C>
 {
@@ -540,12 +584,14 @@ impl<'a, C: cosmwasm_std::CustomQuery> From<(DepsMut<'a, C>, Env, MessageInfo)>
     }
 }
 
+#[allow(deprecated)]
 impl<'a, C: cosmwasm_std::CustomQuery> From<(Deps<'a, C>, Env)> for QueryCtx<'a, C> {
     fn from((deps, env): (Deps<'a, C>, Env)) -> Self {
         Self { deps, env }
     }
 }
 
+#[allow(deprecated)]
 impl<'a, C: cosmwasm_std::CustomQuery> From<(DepsMut<'a, C>, Env)> for SudoCtx<'a, C> {
     fn from((deps, env): (DepsMut<'a, C>, Env)) -> Self {
         Self { deps, env }


Test patch:
diff --git a/examples/contracts/cw20-base/src/multitest/receiver.rs b/examples/contracts/cw20-base/src/multitest/receiver.rs
index 40ad75d..2f6099d 100644
--- a/examples/contracts/cw20-base/src/multitest/receiver.rs
+++ b/examples/contracts/cw20-base/src/multitest/receiver.rs
@@ -1,6 +1,6 @@
+use sylvia::ctx::ExecCtx;
 use sylvia::cw_std::{Binary, Response, StdError, Uint128};
 use sylvia::interface;
-use sylvia::types::ExecCtx;
 
 #[interface]
 #[sv::custom(msg=sylvia::cw_std::Empty, query=sylvia::cw_std::Empty)]
@@ -19,8 +19,8 @@ pub trait Receiver {
 
 pub mod impl_receiver {
     use crate::multitest::receiver_contract::ReceiverContract;
+    use sylvia::ctx::ExecCtx;
     use sylvia::cw_std::{Response, StdError};
-    use sylvia::types::ExecCtx;
 
     impl super::Receiver for ReceiverContract {
         type Error = StdError;
diff --git a/examples/contracts/cw20-base/src/multitest/receiver_contract.rs b/examples/contracts/cw20-base/src/multitest/receiver_contract.rs
index 643bc62..43753c1 100644
--- a/examples/contracts/cw20-base/src/multitest/receiver_contract.rs
+++ b/examples/contracts/cw20-base/src/multitest/receiver_contract.rs
@@ -1,6 +1,6 @@
 use sylvia::contract;
+use sylvia::ctx::InstantiateCtx;
 use sylvia::cw_std::{Response, StdResult};
-use sylvia::types::InstantiateCtx;
 
 use super::receiver;
 pub struct ReceiverContract {}
diff --git a/sylvia/src/multitest.rs b/sylvia/src/multitest.rs
index 39ad4f8..7be78fd 100644
--- a/sylvia/src/multitest.rs
+++ b/sylvia/src/multitest.rs
@@ -3,7 +3,7 @@
 //! ## Example usage:
 //! ```rust
 //! # use sylvia::cw_std::{Response, StdResult};
-//! # use sylvia::types::{ExecCtx, InstantiateCtx, QueryCtx};
+//! # use sylvia::ctx::{ExecCtx, InstantiateCtx, QueryCtx};
 //! pub struct SvContract;
 //!
 //! ##[sylvia::contract]
@@ -56,7 +56,7 @@ use std::marker::PhantomData;
 
 use cosmwasm_std::testing::MockApi;
 use cosmwasm_std::{
-    Addr, Api, BlockInfo, Coin, Empty, Querier, QuerierResult, QuerierWrapper, Storage,
+    Addr, Api, BlockInfo, Coin, Empty, Querier, QuerierResult, QuerierWrapper, StdError, Storage,
 };
 #[cfg(feature = "cosmwasm_1_2")]
 use cosmwasm_std::{CodeInfoResponse, StdResult};
@@ -267,7 +267,7 @@ where
 impl<'a, 'app, Error, Msg, MtApp, ExecC> ExecProxy<'a, 'app, Error, Msg, MtApp, ExecC>
 where
     Msg: Serialize + Debug,
-    Error: Debug + Display + Send + Sync + 'static,
+    Error: From<StdError> + Debug + Display + Send + Sync + 'static,
     ExecC: cosmwasm_std::CustomMsg + 'static,
     MtApp: Executor<ExecC>,
 {
@@ -297,7 +297,15 @@ where
                 &self.msg,
                 self.funds,
             )
-            .map_err(|err| err.downcast().unwrap())
+            .map_err(|err| {
+                if err.is::<Error>() {
+                    err.downcast::<Error>().unwrap()
+                } else if err.is::<StdError>() {
+                    err.downcast::<StdError>().unwrap().into()
+                } else {
+                    StdError::generic_err(err.to_string()).into()
+                }
+            })
     }
 }
 
diff --git a/sylvia/tests/api.rs b/sylvia/tests/api.rs
index 903f24e..f73c0db 100644
--- a/sylvia/tests/api.rs
+++ b/sylvia/tests/api.rs
@@ -1,9 +1,8 @@
 use std::marker::PhantomData;
 use sylvia::cw_std::{Response, StdResult};
 
-use sylvia::types::{
-    CustomMsg, CustomQuery, ExecCtx, InstantiateCtx, MigrateCtx, QueryCtx, SudoCtx,
-};
+use sylvia::ctx::{ExecCtx, InstantiateCtx, MigrateCtx, QueryCtx, SudoCtx};
+use sylvia::types::{CustomMsg, CustomQuery};
 use sylvia_derive::contract;
 
 pub struct SomeContract<Instantiate, Query, Exec, Migrate, Sudo, Ret, CMsg, CQuery> {
diff --git a/sylvia/tests/custom_msg.rs b/sylvia/tests/custom_msg.rs
index d553ba3..6b1626b 100644
--- a/sylvia/tests/custom_msg.rs
+++ b/sylvia/tests/custom_msg.rs
@@ -1,7 +1,7 @@
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
+use sylvia::ctx::{ExecCtx, InstantiateCtx, MigrateCtx, QueryCtx, SudoCtx};
 use sylvia::cw_std::{CustomMsg, Response, StdResult};
-use sylvia::types::{ExecCtx, InstantiateCtx, MigrateCtx, QueryCtx, SudoCtx};
 use sylvia::{contract, entry_points};
 
 #[derive(Clone, PartialEq, Serialize, Deserialize, Debug, JsonSchema)]
@@ -20,9 +20,9 @@ pub struct MyContract;
 pub struct SomeResponse;
 
 mod some_interface {
+    use sylvia::ctx::{ExecCtx, QueryCtx, SudoCtx};
     use sylvia::cw_std::{Response, StdError, StdResult};
     use sylvia::interface;
-    use sylvia::types::{ExecCtx, QueryCtx, SudoCtx};
 
     use crate::{MyMsg, SomeResponse};
 
@@ -43,8 +43,8 @@ mod some_interface {
 }
 
 mod impl_some_interface {
+    use sylvia::ctx::{ExecCtx, QueryCtx, SudoCtx};
     use sylvia::cw_std::{Response, StdError, StdResult};
-    use sylvia::types::{ExecCtx, QueryCtx, SudoCtx};
 
     use crate::some_interface::SomeInterface;
     use crate::{MyMsg, SomeResponse};
@@ -69,9 +69,9 @@ mod impl_some_interface {
 // Use `#[sv::custom(..)]` if both it and associated type defined
 mod interface {
     use crate::MyMsg;
+    use sylvia::ctx::{ExecCtx, QueryCtx, SudoCtx};
     use sylvia::cw_std::{CustomMsg, Response, StdError, StdResult};
     use sylvia::interface;
-    use sylvia::types::{ExecCtx, QueryCtx, SudoCtx};
 
     #[interface]
     #[sv::custom(msg=MyMsg, query=sylvia::cw_std::Empty)]
@@ -92,8 +92,8 @@ mod interface {
 mod impl_interface {
     use crate::interface::Interface;
     use crate::{MyMsg, OtherMsg};
+    use sylvia::ctx::{ExecCtx, QueryCtx, SudoCtx};
     use sylvia::cw_std::{Response, StdError, StdResult};
-    use sylvia::types::{ExecCtx, QueryCtx, SudoCtx};
 
     impl Interface for crate::MyContract {
         type Error = StdError;
@@ -114,9 +114,9 @@ mod impl_interface {
 }
 
 mod other_interface {
+    use sylvia::ctx::{ExecCtx, QueryCtx, SudoCtx};
     use sylvia::cw_std::{Response, StdError, StdResult};
     use sylvia::interface;
-    use sylvia::types::{ExecCtx, QueryCtx, SudoCtx};
 
     #[interface]
     #[sv::custom(msg=sylvia::cw_std::Empty, query=sylvia::cw_std::Empty)]
@@ -135,8 +135,8 @@ mod other_interface {
 }
 mod impl_other_interface {
     use crate::other_interface::OtherInterface;
+    use sylvia::ctx::{ExecCtx, QueryCtx, SudoCtx};
     use sylvia::cw_std::{Response, StdError, StdResult};
-    use sylvia::types::{ExecCtx, QueryCtx, SudoCtx};
 
     impl OtherInterface for crate::MyContract {
         type Error = StdError;
@@ -157,9 +157,9 @@ mod impl_other_interface {
 
 mod associated_interface {
     use crate::SomeResponse;
+    use sylvia::ctx::{ExecCtx, QueryCtx, SudoCtx};
     use sylvia::cw_std::{CustomMsg, Response, StdError, StdResult};
     use sylvia::interface;
-    use sylvia::types::{ExecCtx, QueryCtx, SudoCtx};
 
     #[interface]
     #[sv::custom(query=sylvia::cw_std::Empty)]
@@ -180,8 +180,8 @@ mod associated_interface {
 mod impl_associated_interface {
     use crate::associated_interface::AssociatedInterface;
     use crate::{MyMsg, SomeResponse};
+    use sylvia::ctx::{ExecCtx, QueryCtx, SudoCtx};
     use sylvia::cw_std::{Response, StdError, StdResult};
-    use sylvia::types::{ExecCtx, QueryCtx, SudoCtx};
 
     impl AssociatedInterface for crate::MyContract {
         type Error = StdError;
diff --git a/sylvia/tests/custom_query.rs b/sylvia/tests/custom_query.rs
index 1eaf7af..f22cb6a 100644
--- a/sylvia/tests/custom_query.rs
+++ b/sylvia/tests/custom_query.rs
@@ -1,6 +1,6 @@
 use cosmwasm_schema::cw_serde;
+use sylvia::ctx::{ExecCtx, InstantiateCtx, MigrateCtx, QueryCtx, SudoCtx};
 use sylvia::cw_std::{CustomQuery, Response, StdResult};
-use sylvia::types::{ExecCtx, InstantiateCtx, MigrateCtx, QueryCtx, SudoCtx};
 use sylvia::{contract, entry_points};
 
 #[cw_serde]
@@ -19,9 +19,9 @@ pub struct MyContract;
 pub struct SomeResponse;
 
 mod interface {
+    use sylvia::ctx::{ExecCtx, QueryCtx, SudoCtx};
     use sylvia::cw_std::{CustomQuery, Response, StdError, StdResult};
     use sylvia::interface;
-    use sylvia::types::{ExecCtx, QueryCtx, SudoCtx};
 
     use crate::{MyQuery, SomeResponse};
 
@@ -43,8 +43,8 @@ mod interface {
 }
 
 mod impl_interface {
+    use sylvia::ctx::{ExecCtx, QueryCtx, SudoCtx};
     use sylvia::cw_std::{Response, StdError, StdResult};
-    use sylvia::types::{ExecCtx, QueryCtx, SudoCtx};
 
     use crate::{MyQuery, OtherQuery, SomeResponse};
 
@@ -67,9 +67,9 @@ mod impl_interface {
 }
 
 mod some_interface {
+    use sylvia::ctx::{ExecCtx, QueryCtx, SudoCtx};
     use sylvia::cw_std::{Response, StdError, StdResult};
     use sylvia::interface;
-    use sylvia::types::{ExecCtx, QueryCtx, SudoCtx};
 
     use crate::{MyQuery, SomeResponse};
 
@@ -90,8 +90,8 @@ mod some_interface {
 }
 
 mod impl_some_interface {
+    use sylvia::ctx::{ExecCtx, QueryCtx, SudoCtx};
     use sylvia::cw_std::{Response, StdError, StdResult};
-    use sylvia::types::{ExecCtx, QueryCtx, SudoCtx};
 
     use crate::{MyQuery, SomeResponse};
 
@@ -113,9 +113,9 @@ mod impl_some_interface {
 }
 
 mod associated_type_interface {
+    use sylvia::ctx::{ExecCtx, QueryCtx, SudoCtx};
     use sylvia::cw_std::{CustomQuery, Response, StdError, StdResult};
     use sylvia::interface;
-    use sylvia::types::{ExecCtx, QueryCtx, SudoCtx};
 
     use crate::SomeResponse;
 
@@ -139,8 +139,8 @@ mod associated_type_interface {
 mod impl_associated_type_interface {
     use crate::associated_type_interface::AssociatedTypeInterface;
     use crate::{MyQuery, SomeResponse};
+    use sylvia::ctx::{ExecCtx, QueryCtx, SudoCtx};
     use sylvia::cw_std::{Response, StdError, StdResult};
-    use sylvia::types::{ExecCtx, QueryCtx, SudoCtx};
 
     impl AssociatedTypeInterface for crate::MyContract {
         type Error = StdError;
@@ -161,9 +161,9 @@ mod impl_associated_type_interface {
 }
 
 mod default_query_interface {
+    use sylvia::ctx::{ExecCtx, QueryCtx, SudoCtx};
     use sylvia::cw_std::{Response, StdError, StdResult};
     use sylvia::interface;
-    use sylvia::types::{ExecCtx, QueryCtx, SudoCtx};
 
     use crate::SomeResponse;
 
@@ -186,8 +186,8 @@ mod default_query_interface {
 mod impl_default_query_interface {
     use crate::default_query_interface::DefaultQueryInterface;
     use crate::SomeResponse;
+    use sylvia::ctx::{ExecCtx, QueryCtx, SudoCtx};
     use sylvia::cw_std::{Response, StdError, StdResult};
-    use sylvia::types::{ExecCtx, QueryCtx, SudoCtx};
 
     impl DefaultQueryInterface for crate::MyContract {
         type Error = StdError;
diff --git a/sylvia/tests/dispatching.rs b/sylvia/tests/dispatching.rs
index 985fab9..b46e1ec 100644
--- a/sylvia/tests/dispatching.rs
+++ b/sylvia/tests/dispatching.rs
@@ -18,9 +18,9 @@ pub struct QueryResponse {
 }
 
 mod interface {
+    use sylvia::ctx::{ExecCtx, QueryCtx, SudoCtx};
     use sylvia::cw_std::{Addr, Decimal, Response, StdError};
     use sylvia::interface;
-    use sylvia::types::{ExecCtx, QueryCtx, SudoCtx};
 
     use crate::{EmptyQueryResponse, QueryResponse};
 
@@ -57,8 +57,8 @@ mod interface {
 }
 
 mod impl_interface {
+    use sylvia::ctx::{ExecCtx, QueryCtx, SudoCtx};
     use sylvia::cw_std::{Addr, Decimal, Response, StdError};
-    use sylvia::types::{ExecCtx, QueryCtx, SudoCtx};
 
     use crate::{EmptyQueryResponse, QueryResponse};
 
@@ -127,8 +127,8 @@ mod contract {
     use std::cell::RefCell;
 
     use cw_storage_plus::{Item, Map};
+    use sylvia::ctx::{InstantiateCtx, SudoCtx};
     use sylvia::cw_std::{Addr, Response, StdError, StdResult};
-    use sylvia::types::{InstantiateCtx, SudoCtx};
     use sylvia_derive::{contract, entry_points};
 
     use crate::QueryResponse;
diff --git a/sylvia/tests/entry_points.rs b/sylvia/tests/entry_points.rs
index f3657e3..e74ed5b 100644
--- a/sylvia/tests/entry_points.rs
+++ b/sylvia/tests/entry_points.rs
@@ -8,8 +8,8 @@ pub struct CountResponse {
 
 pub mod sudo {
     use cosmwasm_schema::cw_serde;
+    use sylvia::ctx::SudoCtx;
     use sylvia::cw_std::{Coin, DepsMut, Env, Response, StdError, StdResult};
-    use sylvia::types::SudoCtx;
 
     #[cw_serde]
     pub enum SudoMsg {
@@ -61,8 +61,8 @@ pub mod migrate {
 
 pub mod exec {
     use cosmwasm_schema::cw_serde;
+    use sylvia::ctx::ExecCtx;
     use sylvia::cw_std::{DepsMut, Env, MessageInfo, Response, StdError, StdResult};
-    use sylvia::types::ExecCtx;
 
     use crate::contract::Contract;
 
@@ -134,8 +134,8 @@ pub mod entry_points {
 
 mod contract {
     use cw_storage_plus::Item;
+    use sylvia::ctx::{ExecCtx, InstantiateCtx, MigrateCtx, QueryCtx, SudoCtx};
     use sylvia::cw_std::{Response, StdError, StdResult};
-    use sylvia::types::{ExecCtx, InstantiateCtx, MigrateCtx, QueryCtx, SudoCtx};
     use sylvia::{contract, entry_points};
 
     use crate::CountResponse;
diff --git a/sylvia/tests/executor.rs b/sylvia/tests/executor.rs
index 88495c5..1bfff7d 100644
--- a/sylvia/tests/executor.rs
+++ b/sylvia/tests/executor.rs
@@ -3,8 +3,8 @@
 use cw_storage_plus::Item;
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
+use sylvia::ctx::InstantiateCtx;
 use sylvia::cw_std::{Addr, Response, StdResult};
-use sylvia::types::InstantiateCtx;
 use sylvia::{contract, entry_points};
 
 #[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug, Default)]
@@ -13,9 +13,9 @@ pub struct CountResponse {
 }
 
 pub mod counter {
+    use sylvia::ctx::{ExecCtx, QueryCtx};
     use sylvia::cw_std::{Response, StdError, StdResult};
     use sylvia::interface;
-    use sylvia::types::{ExecCtx, QueryCtx};
 
     use crate::CountResponse;
 
@@ -35,8 +35,8 @@ pub mod counter {
 use counter::sv::Executor as InterfaceExecutor;
 use counter::Counter;
 use sv::Executor as ContractExecutor;
+use sylvia::ctx::{ExecCtx, QueryCtx};
 use sylvia::cw_std::StdError;
-use sylvia::types::{ExecCtx, QueryCtx};
 
 impl Counter for CounterContract {
     type Error = StdError;
diff --git a/sylvia/tests/interface_impl.rs b/sylvia/tests/interface_impl.rs
index aa45a82..c277ea9 100644
--- a/sylvia/tests/interface_impl.rs
+++ b/sylvia/tests/interface_impl.rs
@@ -1,11 +1,13 @@
+use sylvia::ctx::{ExecCtx, InstantiateCtx, QueryCtx, SudoCtx};
 use sylvia::cw_std::{Empty, Response, StdError, StdResult};
-use sylvia::types::{CustomMsg, CustomQuery, ExecCtx, InstantiateCtx, QueryCtx, SudoCtx};
+use sylvia::types::{CustomMsg, CustomQuery};
 use sylvia::{contract, entry_points};
 
 mod interface1 {
+    use sylvia::ctx::{ExecCtx, QueryCtx, SudoCtx};
     use sylvia::cw_std::{Response, StdError, StdResult};
     use sylvia::interface;
-    use sylvia::types::{CustomMsg, CustomQuery, ExecCtx, QueryCtx, SudoCtx};
+    use sylvia::types::{CustomMsg, CustomQuery};
 
     #[interface]
     pub trait SylviaInterface1 {
@@ -34,9 +36,10 @@ mod interface1 {
 }
 
 mod interface2 {
+    use sylvia::ctx::{ExecCtx, QueryCtx, SudoCtx};
     use sylvia::cw_std::{Response, StdError, StdResult};
     use sylvia::interface;
-    use sylvia::types::{CustomMsg, CustomQuery, ExecCtx, QueryCtx, SudoCtx};
+    use sylvia::types::{CustomMsg, CustomQuery};
 
     #[interface]
     pub trait SylviaInterface2 {
diff --git a/sylvia/tests/replies.rs b/sylvia/tests/legacy_replies.rs
similarity index 89%
rename from sylvia/tests/replies.rs
rename to sylvia/tests/legacy_replies.rs
index 663b255..af00f66 100644
--- a/sylvia/tests/replies.rs
+++ b/sylvia/tests/legacy_replies.rs
@@ -5,7 +5,7 @@ use sylvia::cw_std::{from_json, Reply, SubMsgResponse, SubMsgResult};
 
 #[allow(dead_code)]
 mod noop_contract {
-    use sylvia::types::{ExecCtx, InstantiateCtx};
+    use sylvia::ctx::{ExecCtx, InstantiateCtx};
     use sylvia::{contract, entry_points};
 
     use sylvia::cw_std::{Response, StdResult};
@@ -32,10 +32,13 @@ mod noop_contract {
 }
 
 mod reply_contract {
-    use sylvia::types::{ExecCtx, InstantiateCtx, ReplyCtx};
+    use cosmwasm_std::Reply;
+    use sylvia::ctx::{ExecCtx, InstantiateCtx};
+    #[allow(deprecated)]
+    use sylvia::types::ReplyCtx;
     use sylvia::{contract, entry_points};
 
-    use sylvia::cw_std::{to_json_binary, Reply, Response, StdResult, SubMsg, WasmMsg};
+    use sylvia::cw_std::{to_json_binary, Response, StdResult, SubMsg, WasmMsg};
 
     use super::noop_contract;
 
@@ -69,7 +72,8 @@ mod reply_contract {
         }
 
         #[sv::msg(reply)]
-        fn reply(&self, _ctx: ReplyCtx, _msg: Reply) -> StdResult<Response> {
+        #[allow(deprecated)]
+        fn reply(&self, _ctx: ReplyCtx, _reply: Reply) -> StdResult<Response> {
             let resp = Response::new().set_data(to_json_binary("data")?);
             Ok(resp)
         }
diff --git a/sylvia/tests/messages_generation.rs b/sylvia/tests/messages_generation.rs
index a752ee6..86ed0ba 100644
--- a/sylvia/tests/messages_generation.rs
+++ b/sylvia/tests/messages_generation.rs
@@ -17,9 +17,9 @@ pub struct MyQuery;
 impl CustomQuery for MyQuery {}
 
 pub mod interface {
+    use sylvia::ctx::{ExecCtx, QueryCtx, SudoCtx};
     use sylvia::cw_std::{Addr, Decimal, Response, StdError};
     use sylvia::interface;
-    use sylvia::types::{ExecCtx, QueryCtx, SudoCtx};
     use thiserror::Error;
 
     use crate::QueryResult;
@@ -65,9 +65,10 @@ pub mod interface {
 }
 
 mod contract {
+    use cosmwasm_std::{Binary, SubMsgResult};
     use sylvia::contract;
-    use sylvia::cw_std::{Addr, Reply, Response, StdResult};
-    use sylvia::types::{ExecCtx, InstantiateCtx, MigrateCtx, QueryCtx, ReplyCtx, SudoCtx};
+    use sylvia::ctx::{ExecCtx, InstantiateCtx, MigrateCtx, QueryCtx, ReplyCtx, SudoCtx};
+    use sylvia::cw_std::{Addr, Response, StdResult};
     use sylvia_derive::entry_points;
     use thiserror::Error;
 
@@ -86,6 +87,7 @@ mod contract {
     #[sv::msg_attr(instantiate, error("Instantiate"))]
     #[sv::msg_attr(migrate, derive(PartialOrd, Error))]
     #[sv::msg_attr(migrate, error("Migrate"))]
+    #[sv::features(replies)]
     impl Contract {
         #[allow(clippy::new_without_default)]
         #[allow(dead_code)]
@@ -147,7 +149,12 @@ mod contract {
         }
 
         #[sv::msg(reply)]
-        fn my_reply(&self, _ctx: ReplyCtx<MyQuery>, _reply: Reply) -> StdResult<Response> {
+        fn my_reply(
+            &self,
+            _ctx: ReplyCtx<MyQuery>,
+            _result: SubMsgResult,
+            #[sv::payload(raw)] _payload: Binary,
+        ) -> StdResult<Response> {
             Ok(Response::new())
         }
 
diff --git a/sylvia/tests/multitest.rs b/sylvia/tests/multitest.rs
index 5146e8a..cfc5825 100644
--- a/sylvia/tests/multitest.rs
+++ b/sylvia/tests/multitest.rs
@@ -2,10 +2,10 @@
 
 use cw_multi_test::IntoBech32;
 use std::marker::PhantomData;
+use sylvia::ctx::InstantiateCtx;
 use sylvia::cw_std::{CodeInfoResponse, Empty, Response, StdResult};
 use sylvia::entry_points;
 use sylvia::multitest::App;
-use sylvia::types::InstantiateCtx;
 use sylvia_derive::contract;
 
 pub struct SomeContract<ParamT> {
diff --git a/sylvia/tests/querier.rs b/sylvia/tests/querier.rs
index 78ce413..7f27af8 100644
--- a/sylvia/tests/querier.rs
+++ b/sylvia/tests/querier.rs
@@ -3,8 +3,8 @@
 use cw_storage_plus::Item;
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
+use sylvia::ctx::InstantiateCtx;
 use sylvia::cw_std::{Addr, Response, StdResult};
-use sylvia::types::InstantiateCtx;
 use sylvia::{contract, entry_points};
 
 #[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug, Default)]
@@ -13,9 +13,9 @@ pub struct CountResponse {
 }
 
 pub mod counter {
+    use sylvia::ctx::{ExecCtx, QueryCtx};
     use sylvia::cw_std::{Response, StdError, StdResult};
     use sylvia::interface;
-    use sylvia::types::{ExecCtx, QueryCtx};
 
     use crate::CountResponse;
 
@@ -42,8 +42,8 @@ pub mod impl_counter {
     use crate::counter::sv::Querier;
     use crate::counter::Counter;
     use crate::CountResponse;
+    use sylvia::ctx::{ExecCtx, QueryCtx};
     use sylvia::cw_std::{Response, StdError, StdResult};
-    use sylvia::types::{ExecCtx, QueryCtx};
 
     impl Counter for super::CounterContract {
         type Error = StdError;
diff --git a/sylvia/tests/query_returns.rs b/sylvia/tests/query_returns.rs
index 6cfd5e1..f51a2f2 100644
--- a/sylvia/tests/query_returns.rs
+++ b/sylvia/tests/query_returns.rs
@@ -2,7 +2,7 @@ use sylvia::cw_std::{Response, StdError, StdResult};
 
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
-use sylvia::types::{InstantiateCtx, QueryCtx};
+use sylvia::ctx::{InstantiateCtx, QueryCtx};
 use sylvia::{contract, entry_points};
 use thiserror::Error;
 
@@ -18,9 +18,9 @@ pub struct QueryResponse;
 type QueryResult<E> = Result<QueryResponse, E>;
 
 pub mod msg {
+    use sylvia::ctx::QueryCtx;
     use sylvia::cw_std::StdError;
     use sylvia::interface;
-    use sylvia::types::QueryCtx;
 
     use crate::{QueryResponse, QueryResult};
 
diff --git a/sylvia/tests/remote.rs b/sylvia/tests/remote.rs
index 1c43322..ccc3837 100644
--- a/sylvia/tests/remote.rs
+++ b/sylvia/tests/remote.rs
@@ -15,9 +15,10 @@ impl sylvia::cw_std::CustomQuery for ExampleQuery {}
 pub mod counter {
     use serde::de::DeserializeOwned;
     use serde::Serialize;
+    use sylvia::ctx::{ExecCtx, QueryCtx};
     use sylvia::cw_std::{Response, StdError};
     use sylvia::interface;
-    use sylvia::types::{CustomMsg, CustomQuery, ExecCtx, QueryCtx};
+    use sylvia::types::{CustomMsg, CustomQuery};
 
     #[interface]
     pub trait Counter {
@@ -41,8 +42,8 @@ pub mod counter {
 pub mod signed_contract {
     use cw_storage_plus::Item;
     use sylvia::contract;
+    use sylvia::ctx::{ExecCtx, InstantiateCtx, QueryCtx};
     use sylvia::cw_std::{Response, StdError, StdResult};
-    use sylvia::types::{ExecCtx, InstantiateCtx, QueryCtx};
 
     use crate::counter::Counter;
     use crate::{ExampleMsg, ExampleQuery};
@@ -100,8 +101,8 @@ pub mod signed_contract {
 pub mod unsigned_contract {
     use cw_storage_plus::Item;
     use sylvia::contract;
+    use sylvia::ctx::{ExecCtx, InstantiateCtx, QueryCtx};
     use sylvia::cw_std::{Response, StdError, StdResult};
-    use sylvia::types::{ExecCtx, InstantiateCtx, QueryCtx};
 
     use crate::counter::Counter;
     use crate::{ExampleMsg, ExampleQuery};
@@ -156,7 +157,9 @@ pub mod unsigned_contract {
     }
 }
 
-// Making sure `Remote` can be stored in `#[cw_serde]` types
+// Making sure `Remote` can be stored in `#[cw_serde]` types.
+// This is intentionally a dead code.
+// https://github.com/CosmWasm/sylvia/issues/181
 #[cw_serde]
 #[allow(dead_code)]
 pub struct ContractStorage<Contract> {
@@ -192,8 +195,8 @@ pub mod manager {
     use schemars::JsonSchema;
     use serde::de::DeserializeOwned;
     use serde::Serialize;
+    use sylvia::ctx::{ExecCtx, InstantiateCtx, QueryCtx};
     use sylvia::cw_std::{Addr, Response, StdError, StdResult};
-    use sylvia::types::{ExecCtx, InstantiateCtx, QueryCtx};
     use sylvia::{contract, entry_points};
 
     use crate::counter::sv::{Executor, Querier};
@@ -257,12 +260,52 @@ pub mod manager {
 
             Ok(count)
         }
+
+        #[sv::msg(exec)]
+        fn update_admin(
+            &self,
+            ctx: ExecCtx<ExampleQuery>,
+            new_admin: String,
+        ) -> Result<Response<ExampleMsg>, StdError> {
+            let wasm = self
+                .remote_counter
+                .load(ctx.deps.storage)?
+                .interface_remote
+                .update_admin(&new_admin);
+            let resp = Response::new().add_message(wasm);
+            Ok(resp)
+        }
+
+        #[sv::msg(exec)]
+        fn clear_admin(
+            &self,
+            ctx: ExecCtx<ExampleQuery>,
+        ) -> Result<Response<ExampleMsg>, StdError> {
+            let wasm = self
+                .remote_counter
+                .load(ctx.deps.storage)?
+                .interface_remote
+                .clear_admin();
+            let resp = Response::new().add_message(wasm);
+            Ok(resp)
+        }
+
+        #[sv::msg(query)]
+        fn counter_contract(&self, ctx: QueryCtx<ExampleQuery>) -> Result<Addr, StdError> {
+            Ok(self
+                .remote_counter
+                .load(ctx.deps.storage)?
+                .interface_remote
+                .as_ref()
+                .clone())
+        }
     }
 }
 
 #[cfg(test)]
 mod tests {
-    use cw_multi_test::{BasicApp, IntoBech32};
+    use cosmwasm_std::{CosmosMsg, WasmMsg};
+    use cw_multi_test::{BasicApp, Executor, IntoAddr};
     use sylvia::cw_std::{Addr, StdError};
     use sylvia::multitest::{App, Proxy};
     use sylvia::types::Remote;
@@ -277,7 +320,6 @@ mod tests {
     use crate::{ExampleMsg, ExampleQuery};
 
     type ExampleApp = BasicApp<ExampleMsg, ExampleQuery>;
-    const OWNER: &str = "owner";
 
     #[test]
     fn remote_generation() {
@@ -302,11 +344,12 @@ mod tests {
         assert_eq!(&addr, borrowed_remote.as_ref());
     }
 
-    fn setup(
-        app: &App<ExampleApp>,
+    fn setup<'a>(
+        app: &'a App<ExampleApp>,
+        owner: &'a Addr,
     ) -> (
-        Proxy<ExampleApp, ManagerContract<i32>>,
-        Proxy<ExampleApp, ManagerContract<u32>>,
+        Proxy<'a, ExampleApp, ManagerContract<i32>>,
+        Proxy<'a, ExampleApp, ManagerContract<u32>>,
     ) {
         // Manager operating on signed numbers
         let signed_counter_code_id = SignedCounterCodeId::store_code(app);
@@ -314,7 +357,7 @@ mod tests {
         let signed_counter_contract = signed_counter_code_id
             .instantiate()
             .with_label("Signed counter contract")
-            .call(&OWNER.into_bech32())
+            .call(owner)
             .unwrap();
 
         let manager_code_id = ManagerCodeId::store_code(app);
@@ -322,7 +365,7 @@ mod tests {
         let signed_manager_contract = manager_code_id
             .instantiate(signed_counter_contract.contract_addr.clone())
             .with_label("Manager contract")
-            .call(&OWNER.into_bech32())
+            .call(owner)
             .unwrap();
 
         // Manager operating on unsigned numbers
@@ -331,7 +374,8 @@ mod tests {
         let unsigned_counter_contract = unsigned_counter_code_id
             .instantiate()
             .with_label("Unsigned counter contract")
-            .call(&OWNER.into_bech32())
+            .with_admin(Some(owner.as_str()))
+            .call(owner)
             .unwrap();
 
         let manager_code_id = ManagerCodeId::store_code(app);
@@ -339,7 +383,18 @@ mod tests {
         let unsigned_manager_contract = manager_code_id
             .instantiate(unsigned_counter_contract.contract_addr.clone())
             .with_label("Manager contract")
-            .call(&OWNER.into_bech32())
+            .call(owner)
+            .unwrap();
+
+        // Set manager contract as an admin of the counter contract
+        app.app_mut()
+            .execute(
+                owner.clone(),
+                CosmosMsg::Wasm(WasmMsg::UpdateAdmin {
+                    contract_addr: unsigned_counter_contract.contract_addr.to_string(),
+                    admin: unsigned_manager_contract.contract_addr.to_string(),
+                }),
+            )
             .unwrap();
 
         (signed_manager_contract, unsigned_manager_contract)
@@ -347,23 +402,81 @@ mod tests {
 
     #[test]
     fn call_remote() {
+        let owner = "owner".into_addr();
         let app = App::<cw_multi_test::BasicApp<ExampleMsg, ExampleQuery>>::custom(|_, _, _| {});
-        let (signed_manager_contract, unsigned_manager_contract) = setup(&app);
+        let (signed_manager_contract, unsigned_manager_contract) = setup(&app, &owner);
 
         assert_eq!(signed_manager_contract.count().unwrap(), 0);
 
-        signed_manager_contract
-            .add(5)
-            .call(&OWNER.into_bech32())
-            .unwrap();
+        signed_manager_contract.add(5).call(&owner).unwrap();
         assert_eq!(signed_manager_contract.count().unwrap(), 5);
 
         assert_eq!(unsigned_manager_contract.count().unwrap(), 0);
 
+        unsigned_manager_contract.add(5).call(&owner).unwrap();
+        assert_eq!(unsigned_manager_contract.count().unwrap(), 5);
+    }
+
+    #[test]
+    fn update_admin() {
+        let owner = "owner".into_addr();
+        let app = App::<cw_multi_test::BasicApp<ExampleMsg, ExampleQuery>>::custom(|_, _, _| {});
+        let (_, unsigned_manager_contract) = setup(&app, &owner);
+        let new_admin = "new_admin".into_addr();
+
+        let unsigned_counter_contract_addr = unsigned_manager_contract.counter_contract().unwrap();
+
+        // Initial admin should be the manager_contract
+        let contract_info = app
+            .querier()
+            .query_wasm_contract_info(unsigned_counter_contract_addr.clone())
+            .unwrap();
+        assert_eq!(
+            contract_info.admin,
+            Some(unsigned_manager_contract.contract_addr.clone())
+        );
+
+        // Add new admin
         unsigned_manager_contract
-            .add(5)
-            .call(&OWNER.into_bech32())
+            .update_admin(new_admin.to_string())
+            .call(&owner)
             .unwrap();
-        assert_eq!(unsigned_manager_contract.count().unwrap(), 5);
+
+        let contract_info = app
+            .querier()
+            .query_wasm_contract_info(unsigned_counter_contract_addr)
+            .unwrap();
+        assert_eq!(contract_info.admin, Some(new_admin.clone()));
+    }
+
+    #[test]
+    fn clear_admin() {
+        let owner = "owner".into_addr();
+        let app = App::<cw_multi_test::BasicApp<ExampleMsg, ExampleQuery>>::custom(|_, _, _| {});
+        let (_, unsigned_manager_contract) = setup(&app, &owner);
+
+        let unsigned_counter_contract_addr = unsigned_manager_contract.counter_contract().unwrap();
+
+        // Initial admin should be the manager_contract
+        let contract_info = app
+            .querier()
+            .query_wasm_contract_info(unsigned_counter_contract_addr.clone())
+            .unwrap();
+        assert_eq!(
+            contract_info.admin,
+            Some(unsigned_manager_contract.contract_addr.clone())
+        );
+
+        // Clear admin
+        unsigned_manager_contract
+            .clear_admin()
+            .call(&owner)
+            .unwrap();
+
+        let contract_info = app
+            .querier()
+            .query_wasm_contract_info(unsigned_counter_contract_addr.clone())
+            .unwrap();
+        assert_eq!(contract_info.admin, None);
     }
 }
diff --git a/sylvia/tests/reply_data.rs b/sylvia/tests/reply_data.rs
new file mode 100644
index 0000000..8d282f4
--- /dev/null
+++ b/sylvia/tests/reply_data.rs
@@ -0,0 +1,399 @@
+use cosmwasm_schema::cw_serde;
+use cosmwasm_std::to_json_binary;
+use cw_storage_plus::Item;
+use cw_utils::{MsgInstantiateContractResponse, ParseReplyError};
+use noop_contract::sv::{Executor, NoopContractInstantiateBuilder};
+use sv::SubMsgMethods;
+use sylvia::builder::instantiate::InstantiateBuilder;
+use sylvia::ctx::{ExecCtx, InstantiateCtx, ReplyCtx};
+use sylvia::cw_std::{Addr, Binary, Response, StdError, SubMsg};
+use sylvia::types::Remote;
+use sylvia::{contract, entry_points};
+use thiserror::Error;
+
+#[cw_serde]
+pub struct ComplexData {
+    pub message: String,
+    pub number: u64,
+}
+
+#[allow(dead_code)]
+mod noop_contract {
+    use cosmwasm_std::{to_json_binary, Binary, StdResult};
+    use sylvia::ctx::{ExecCtx, InstantiateCtx};
+    use sylvia::{contract, entry_points};
+
+    use sylvia::cw_std::Response;
+
+    use crate::ComplexData;
+
+    pub struct NoopContract;
+
+    #[entry_points]
+    #[contract]
+    impl NoopContract {
+        pub const fn new() -> Self {
+            Self
+        }
+
+        #[sv::msg(instantiate)]
+        fn instantiate(&self, _ctx: InstantiateCtx) -> StdResult<Response> {
+            Ok(Response::new())
+        }
+
+        #[sv::msg(exec)]
+        fn noop(&self, _ctx: ExecCtx, data: Option<Binary>) -> StdResult<Response> {
+            let resp = match data {
+                Some(data) => Response::new().set_data(data),
+                None => Response::new(),
+            };
+
+            Ok(resp)
+        }
+
+        #[sv::msg(exec)]
+        fn noop_complex_data(&self, _ctx: ExecCtx) -> StdResult<Response> {
+            let data = ComplexData {
+                message: "Hello".to_string(),
+                number: 42,
+            };
+            Ok(Response::new().set_data(to_json_binary(&data)?))
+        }
+    }
+}
+
+#[cw_serde]
+pub struct InstantiatePayload {
+    pub sender: Addr,
+}
+
+#[derive(Error, Debug, PartialEq)]
+pub enum ContractError {
+    #[error("{0}")]
+    Std(#[from] StdError),
+
+    #[error("{0}")]
+    ParseReply(#[from] ParseReplyError),
+}
+
+pub struct Contract {
+    remote: Item<Remote<'static, noop_contract::NoopContract>>,
+}
+
+#[entry_points]
+#[contract]
+#[sv::error(ContractError)]
+#[sv::features(replies)]
+impl Contract {
+    pub fn new() -> Self {
+        Self {
+            remote: Item::new("remote"),
+        }
+    }
+
+    #[sv::msg(instantiate)]
+    pub fn instantiate(
+        &self,
+        ctx: InstantiateCtx,
+        remote_code_id: u64,
+    ) -> Result<Response, ContractError> {
+        // Custom type can be used as a payload.
+        let payload = InstantiatePayload {
+            sender: ctx.info.sender,
+        };
+        let sub_msg = InstantiateBuilder::noop_contract(remote_code_id)?
+            .with_label("noop")
+            .build()
+            .remote_instantiated(payload)?;
+
+        Ok(Response::new().add_submessage(sub_msg))
+    }
+
+    #[sv::msg(exec)]
+    fn send_message_expecting_data(
+        &self,
+        ctx: ExecCtx,
+        data: Option<Binary>,
+        reply_id: u64,
+    ) -> Result<Response, ContractError> {
+        let msg = self
+            .remote
+            .load(ctx.deps.storage)?
+            .executor()
+            .noop(data)?
+            .build();
+        let submsg = SubMsg::reply_on_success(msg, reply_id);
+
+        Ok(Response::new().add_submessage(submsg))
+    }
+
+    #[sv::msg(exec)]
+    fn send_message_expecting_complex_data(&self, ctx: ExecCtx) -> Result<Response, ContractError> {
+        let submsg = self
+            .remote
+            .load(ctx.deps.storage)?
+            .executor()
+            .noop_complex_data()?
+            .build()
+            .complex_data(Binary::default())?;
+
+        Ok(Response::new().add_submessage(submsg))
+    }
+
+    #[sv::msg(reply, reply_on=success)]
+    fn remote_instantiated(
+        &self,
+        ctx: ReplyCtx,
+        #[sv::data(instantiate)] data: MsgInstantiateContractResponse,
+        _instantiate_payload: InstantiatePayload,
+    ) -> Result<Response, ContractError> {
+        let remote_addr = Addr::unchecked(data.contract_address);
+
+        self.remote
+            .save(ctx.deps.storage, &Remote::new(remote_addr))?;
+
+        Ok(Response::new())
+    }
+
+    #[sv::msg(reply, reply_on=success)]
+    fn _optional_remote_instantiated(
+        &self,
+        _ctx: ReplyCtx,
+        #[sv::data(instantiate, opt)] _data: Option<MsgInstantiateContractResponse>,
+        #[sv::payload(raw)] _payload: Binary,
+    ) -> Result<Response, ContractError> {
+        Ok(Response::new())
+    }
+
+    #[sv::msg(reply, reply_on=success)]
+    fn data_raw_opt(
+        &self,
+        _ctx: ReplyCtx,
+        #[sv::data(raw, opt)] _data: Option<Binary>,
+        #[sv::payload(raw)] _payload: Binary,
+    ) -> Result<Response, ContractError> {
+        Ok(Response::new())
+    }
+
+    #[sv::msg(reply, reply_on=success)]
+    fn data_raw(
+        &self,
+        _ctx: ReplyCtx,
+        #[sv::data(raw)] _data: Binary,
+        #[sv::payload(raw)] _payload: Binary,
+    ) -> Result<Response, ContractError> {
+        Ok(Response::new())
+    }
+
+    #[sv::msg(reply, reply_on=success)]
+    fn data_opt(
+        &self,
+        _ctx: ReplyCtx,
+        #[sv::data(opt)] _data: Option<String>,
+        #[sv::payload(raw)] _payload: Binary,
+    ) -> Result<Response, ContractError> {
+        Ok(Response::new())
+    }
+
+    #[sv::msg(reply, reply_on=success)]
+    fn data(
+        &self,
+        _ctx: ReplyCtx,
+        #[sv::data] _data: String,
+        #[sv::payload(raw)] _payload: Binary,
+    ) -> Result<Response, ContractError> {
+        Ok(Response::new())
+    }
+
+    #[sv::msg(reply, reply_on=success)]
+    fn complex_data(
+        &self,
+        _ctx: ReplyCtx,
+        #[sv::data] _data: ComplexData,
+        #[sv::payload(raw)] _payload: Binary,
+    ) -> Result<Response, ContractError> {
+        Ok(Response::new())
+    }
+
+    #[sv::msg(reply, reply_on=success)]
+    fn no_data(
+        &self,
+        _ctx: ReplyCtx,
+        #[sv::payload(raw)] _payload: Binary,
+    ) -> Result<Response, ContractError> {
+        Ok(Response::new())
+    }
+}
+
+use crate::noop_contract::sv::mt::CodeId as NoopCodeId;
+use crate::sv::mt::{CodeId, ContractProxy};
+use crate::sv::{DATA_OPT_REPLY_ID, DATA_RAW_OPT_REPLY_ID, DATA_RAW_REPLY_ID, DATA_REPLY_ID};
+
+use sylvia::cw_multi_test::IntoBech32;
+use sylvia::multitest::App;
+
+#[test]
+fn data_instantiate() {
+    let app = App::default();
+    let code_id = CodeId::store_code(&app);
+    let noop_code_id = NoopCodeId::store_code(&app);
+
+    let owner = "owner".into_bech32();
+
+    // Trigger remote instantiation reply
+    let _ = code_id
+        .instantiate(noop_code_id.code_id())
+        .with_label("Contract")
+        .call(&owner)
+        .unwrap();
+}
+
+#[test]
+fn data_raw_opt() {
+    let app = App::default();
+    let code_id = CodeId::store_code(&app);
+    let noop_code_id = NoopCodeId::store_code(&app);
+
+    let owner = "owner".into_bech32();
+    let data = Some(to_json_binary(&String::from("some_data")).unwrap());
+
+    // Trigger remote instantiation reply
+    let contract = code_id
+        .instantiate(noop_code_id.code_id())
+        .with_label("Contract")
+        .call(&owner)
+        .unwrap();
+
+    // Should forward `data` in every case
+    contract
+        .send_message_expecting_data(None, DATA_RAW_OPT_REPLY_ID)
+        .call(&owner)
+        .unwrap();
+
+    contract
+        .send_message_expecting_data(data.clone(), DATA_RAW_OPT_REPLY_ID)
+        .call(&owner)
+        .unwrap();
+}
+
+#[test]
+fn data_raw() {
+    let app = App::default();
+    let code_id = CodeId::store_code(&app);
+    let noop_code_id = NoopCodeId::store_code(&app);
+
+    let owner = "owner".into_bech32();
+    let data = Some(to_json_binary(&String::from("some_data")).unwrap());
+
+    // Trigger remote instantiation reply
+    let contract = code_id
+        .instantiate(noop_code_id.code_id())
+        .with_label("Contract")
+        .call(&owner)
+        .unwrap();
+
+    // Should forward `data` if `Some` and return error if `None`
+    let err = contract
+        .send_message_expecting_data(None, DATA_RAW_REPLY_ID)
+        .call(&owner)
+        .unwrap_err();
+    assert_eq!(
+        err,
+        StdError::generic_err("Missing reply data field.").into()
+    );
+
+    contract
+        .send_message_expecting_data(data.clone(), DATA_RAW_REPLY_ID)
+        .call(&owner)
+        .unwrap();
+}
+
+#[test]
+fn data_opt() {
+    let app = App::default();
+    let code_id = CodeId::store_code(&app);
+    let noop_code_id = NoopCodeId::store_code(&app);
+
+    let owner = "owner".into_bech32();
+    let data = Some(to_json_binary(&String::from("some_data")).unwrap());
+    let invalid_data = Some(Binary::from("InvalidData".as_bytes()));
+
+    // Trigger remote instantiation reply
+    let contract = code_id
+        .instantiate(noop_code_id.code_id())
+        .with_label("Contract")
+        .call(&owner)
+        .unwrap();
+
+    // Should forward deserialized `data` if `Some` or None and return error if deserialization fails
+    contract
+        .send_message_expecting_data(None, DATA_OPT_REPLY_ID)
+        .call(&owner)
+        .unwrap();
+
+    let err = contract
+        .send_message_expecting_data(invalid_data.clone(), DATA_OPT_REPLY_ID)
+        .call(&owner)
+        .unwrap_err();
+    assert_eq!(
+        err,
+        StdError::generic_err(
+            "Invalid reply data at block height: 12345, transaction id: 0.\nSerde error while deserializing Error parsing into type alloc::string::String: Invalid type"
+        ).into()
+    );
+
+    contract
+        .send_message_expecting_data(data.clone(), DATA_OPT_REPLY_ID)
+        .call(&owner)
+        .unwrap();
+}
+
+#[test]
+fn data() {
+    let app = App::default();
+    let code_id = CodeId::store_code(&app);
+    let noop_code_id = NoopCodeId::store_code(&app);
+
+    let owner = "owner".into_bech32();
+    let data = Some(to_json_binary(&String::from("some_data")).unwrap());
+    let invalid_data = Some(Binary::from("InvalidData".as_bytes()));
+
+    // Trigger remote instantiation reply
+    let contract = code_id
+        .instantiate(noop_code_id.code_id())
+        .with_label("Contract")
+        .call(&owner)
+        .unwrap();
+
+    // Should forward deserialized `data` if `Some` and return error if `None` or if deserialization fails
+    let err = contract
+        .send_message_expecting_data(None, DATA_REPLY_ID)
+        .call(&owner)
+        .unwrap_err();
+    assert_eq!(
+        err,
+        StdError::generic_err("Missing reply data field.").into()
+    );
+
+    let err = contract
+        .send_message_expecting_data(invalid_data, DATA_REPLY_ID)
+        .call(&owner)
+        .unwrap_err();
+    assert_eq!(
+        err,
+        StdError::generic_err(
+            "Invalid reply data at block height: 12345, transaction id: 0.\nSerde error while deserializing Error parsing into type alloc::string::String: Invalid type"
+        ).into()
+    );
+
+    contract
+        .send_message_expecting_data(data, DATA_REPLY_ID)
+        .call(&owner)
+        .unwrap();
+
+    // Should deserialize custom data types
+    contract
+        .send_message_expecting_complex_data()
+        .call(&owner)
+        .unwrap();
+}
diff --git a/sylvia/tests/reply_dispatch.rs b/sylvia/tests/reply_dispatch.rs
new file mode 100644
index 0000000..ab4925e
--- /dev/null
+++ b/sylvia/tests/reply_dispatch.rs
@@ -0,0 +1,366 @@
+use cosmwasm_schema::cw_serde;
+use cosmwasm_std::{BankMsg, CosmosMsg, Empty, SubMsgResult};
+use cw_storage_plus::Item;
+use cw_utils::{parse_instantiate_response_data, ParseReplyError};
+use noop_contract::sv::{Executor, NoopContractInstantiateBuilder};
+use sv::{
+    SubMsgMethods, ALWAYS_REPLY_ID, ERROR_REPLY_ID, REMOTE_INSTANTIATED_REPLY_ID, SUCCESS_REPLY_ID,
+};
+use sylvia::builder::instantiate::InstantiateBuilder;
+use sylvia::ctx::{ExecCtx, InstantiateCtx, QueryCtx, ReplyCtx};
+use sylvia::cw_std::{Addr, Binary, Response, StdError, SubMsg};
+use sylvia::types::{CustomMsg, CustomQuery, Remote};
+use sylvia::{contract, entry_points};
+use thiserror::Error;
+
+#[allow(dead_code)]
+mod noop_contract {
+    use cosmwasm_std::{Empty, StdError, StdResult};
+    use sylvia::ctx::{ExecCtx, InstantiateCtx};
+    use sylvia::types::{CustomMsg, CustomQuery};
+    use sylvia::{contract, entry_points};
+
+    use sylvia::cw_std::Response;
+
+    pub struct NoopContract<M, Q> {
+        _phantom: std::marker::PhantomData<(M, Q)>,
+    }
+
+    #[entry_points(generics<Empty, Empty>)]
+    #[contract]
+    #[sv::custom(msg=M, query=Q)]
+    impl<M, Q> NoopContract<M, Q>
+    where
+        M: CustomMsg + 'static,
+        Q: CustomQuery + 'static,
+    {
+        pub const fn new() -> Self {
+            Self {
+                _phantom: std::marker::PhantomData,
+            }
+        }
+
+        #[sv::msg(instantiate)]
+        fn instantiate(&self, _ctx: InstantiateCtx<Q>) -> StdResult<Response<M>> {
+            Ok(Response::new())
+        }
+
+        #[sv::msg(exec)]
+        fn noop(&self, _ctx: ExecCtx<Q>, should_fail: bool) -> StdResult<Response<M>> {
+            if should_fail {
+                Err(StdError::generic_err("Failed as requested"))
+            } else {
+                Ok(Response::new())
+            }
+        }
+    }
+}
+
+#[cw_serde]
+pub struct InstantiatePayload {
+    pub sender: Addr,
+}
+
+#[derive(Error, Debug, PartialEq)]
+pub enum ContractError {
+    #[error("{0}")]
+    Std(#[from] StdError),
+
+    #[error("{0}")]
+    ParseReply(#[from] ParseReplyError),
+}
+
+pub struct Contract<M, Q> {
+    remote: Item<Remote<'static, noop_contract::NoopContract<Empty, Empty>>>,
+    last_reply: Item<u64>,
+    _phantom: std::marker::PhantomData<(M, Q)>,
+}
+
+#[entry_points(generics<Empty, Empty>)]
+#[contract]
+#[sv::error(ContractError)]
+#[sv::custom(msg=M, query=Q)]
+#[sv::features(replies)]
+impl<M, Q> Contract<M, Q>
+where
+    M: CustomMsg + 'static,
+    Q: CustomQuery + 'static,
+{
+    pub fn new() -> Self {
+        Self {
+            remote: Item::new("remote"),
+            last_reply: Item::new("last_reply"),
+            _phantom: std::marker::PhantomData,
+        }
+    }
+
+    #[sv::msg(instantiate)]
+    pub fn instantiate(
+        &self,
+        ctx: InstantiateCtx<Q>,
+        remote_code_id: u64,
+    ) -> Result<Response<M>, ContractError> {
+        // Custom type can be used as a payload.
+        let payload = InstantiatePayload {
+            sender: ctx.info.sender,
+        };
+        let sub_msg = InstantiateBuilder::noop_contract(remote_code_id)?
+            .with_label("noop")
+            .build()
+            .remote_instantiated(payload)?;
+
+        Ok(Response::new().add_submessage(sub_msg))
+    }
+
+    #[sv::msg(exec)]
+    pub fn call_remote_success(
+        &self,
+        ctx: ExecCtx<Q>,
+        should_fail: bool,
+    ) -> Result<Response<M>, ContractError> {
+        let msg = self
+            .remote
+            .load(ctx.deps.storage)?
+            .executor()
+            .noop(should_fail)?
+            .build()
+            .success(Binary::default())?;
+
+        Ok(Response::new().add_submessage(msg))
+    }
+
+    #[sv::msg(exec)]
+    pub fn call_remote_error(
+        &self,
+        ctx: ExecCtx<Q>,
+        should_fail: bool,
+    ) -> Result<Response<M>, ContractError> {
+        let msg = self
+            .remote
+            .load(ctx.deps.storage)?
+            .executor()
+            .noop(should_fail)?
+            .build()
+            .error(Binary::default())?;
+
+        Ok(Response::new().add_submessage(msg))
+    }
+
+    #[sv::msg(exec)]
+    pub fn call_remote_both(
+        &self,
+        ctx: ExecCtx<Q>,
+        should_fail: bool,
+    ) -> Result<Response<M>, ContractError> {
+        let msg = self
+            .remote
+            .load(ctx.deps.storage)?
+            .executor()
+            .noop(should_fail)?
+            .build()
+            .both(Binary::default())?;
+
+        Ok(Response::new().add_submessage(msg))
+    }
+
+    #[sv::msg(exec)]
+    pub fn call_remote_always(
+        &self,
+        ctx: ExecCtx<Q>,
+        should_fail: bool,
+    ) -> Result<Response<M>, ContractError> {
+        // Tuple can be used as a payload.
+        let msg = self
+            .remote
+            .load(ctx.deps.storage)?
+            .executor()
+            .noop(should_fail)?
+            .build()
+            .always(42_u32, "Hello, world!".to_string())?;
+
+        Ok(Response::new().add_submessage(msg))
+    }
+
+    #[sv::msg(exec)]
+    pub fn call_remote_unknown_id(
+        &self,
+        ctx: ExecCtx<Q>,
+        should_fail: bool,
+        reply_id: u64,
+    ) -> Result<Response<M>, ContractError> {
+        let msg = self
+            .remote
+            .load(ctx.deps.storage)?
+            .executor()
+            .noop(should_fail)?
+            .build();
+
+        let msg = SubMsg::reply_always(msg, reply_id);
+
+        Ok(Response::new().add_submessage(msg))
+    }
+
+    #[sv::msg(query)]
+    pub fn last_reply(&self, ctx: QueryCtx<Q>) -> Result<u64, ContractError> {
+        self.last_reply.load(ctx.deps.storage).map_err(Into::into)
+    }
+
+    #[sv::msg(reply, reply_on=success)]
+    fn remote_instantiated(
+        &self,
+        ctx: ReplyCtx<Q>,
+        #[sv::data(raw, opt)] data: Option<Binary>,
+        instantiate_payload: InstantiatePayload,
+    ) -> Result<Response<M>, ContractError> {
+        self.last_reply
+            .save(ctx.deps.storage, &REMOTE_INSTANTIATED_REPLY_ID)?;
+        let init_data = parse_instantiate_response_data(&data.unwrap())?;
+        let remote_addr = Addr::unchecked(init_data.contract_address);
+        assert_eq!(
+            &instantiate_payload.sender.to_string(),
+            "cosmwasm1fsgzj6t7udv8zhf6zj32mkqhcjcpv52yph5qsdcl0qt94jgdckqs2g053y"
+        );
+
+        self.remote
+            .save(ctx.deps.storage, &Remote::new(remote_addr))?;
+
+        Ok(Response::new())
+    }
+
+    #[sv::msg(reply, handlers=[success, both], reply_on=success)]
+    fn success(
+        &self,
+        ctx: ReplyCtx<Q>,
+        #[sv::data(raw, opt)] _data: Option<Binary>,
+        #[sv::payload(raw)] _payload: Binary,
+    ) -> Result<Response<M>, ContractError> {
+        self.last_reply.save(ctx.deps.storage, &SUCCESS_REPLY_ID)?;
+
+        Ok(Response::new())
+    }
+
+    #[sv::msg(reply, handlers=[error, both], reply_on=error)]
+    fn error(
+        &self,
+        ctx: ReplyCtx<Q>,
+        _error: String,
+        #[sv::payload(raw)] _payload: Binary,
+    ) -> Result<Response<M>, ContractError> {
+        self.last_reply.save(ctx.deps.storage, &ERROR_REPLY_ID)?;
+
+        Ok(Response::new())
+    }
+
+    #[sv::msg(reply, reply_on=always)]
+    fn always(
+        &self,
+        ctx: ReplyCtx<Q>,
+        _result: SubMsgResult,
+        first_part_payload: u32,
+        second_part_payload: String,
+    ) -> Result<Response<M>, ContractError> {
+        self.last_reply.save(ctx.deps.storage, &ALWAYS_REPLY_ID)?;
+        assert_eq!(first_part_payload, 42_u32);
+        assert_eq!(second_part_payload, "Hello, world!".to_string());
+
+        Ok(Response::new())
+    }
+
+    #[sv::msg(exec)]
+    fn send_cosmos_messages(&self, ctx: ExecCtx<Q>) -> Result<Response<M>, ContractError> {
+        let remote_addr = self.remote.load(ctx.deps.storage)?;
+        let cosmos_msg = CosmosMsg::Bank(BankMsg::Send {
+            to_address: remote_addr.as_ref().to_string(),
+            amount: vec![],
+        });
+        let submsg = cosmos_msg.always(42_u32, "Hello, world!".to_string())?;
+        Ok(Response::new().add_submessage(submsg))
+    }
+}
+
+mod tests {
+    use crate::noop_contract::sv::mt::CodeId as NoopCodeId;
+    use crate::sv::mt::{CodeId, ContractProxy};
+    use crate::sv::{
+        ALWAYS_REPLY_ID, ERROR_REPLY_ID, REMOTE_INSTANTIATED_REPLY_ID, SUCCESS_REPLY_ID,
+    };
+
+    use sylvia::cw_multi_test::IntoBech32;
+    use sylvia::cw_std::StdError;
+    use sylvia::multitest::App;
+
+    #[test]
+    fn dispatch_replies() {
+        let app = App::default();
+        let code_id = CodeId::store_code(&app);
+        let noop_code_id = NoopCodeId::store_code(&app);
+
+        let owner = "owner".into_bech32();
+
+        // Trigger remote instantiation reply
+        let contract = code_id
+            .instantiate(noop_code_id.code_id())
+            .with_label("Contract")
+            .call(&owner)
+            .unwrap();
+        let last_reply = contract.last_reply().unwrap();
+        assert_eq!(last_reply, REMOTE_INSTANTIATED_REPLY_ID);
+
+        // Should not dispatch if expected success and execution failed
+        let err = contract.call_remote_success(true).call(&owner).unwrap_err();
+        assert_eq!(err, StdError::generic_err("Failed as requested").into());
+        let last_reply = contract.last_reply().unwrap();
+        assert_eq!(last_reply, REMOTE_INSTANTIATED_REPLY_ID);
+
+        // Should dispatch if expected success and execution succeeded
+        contract.call_remote_success(false).call(&owner).unwrap();
+        let last_reply = contract.last_reply().unwrap();
+        assert_eq!(last_reply, SUCCESS_REPLY_ID);
+
+        // Should not dispatch if expected error and execution succeeded
+        contract.call_remote_error(false).call(&owner).unwrap();
+        let last_reply = contract.last_reply().unwrap();
+        assert_eq!(last_reply, SUCCESS_REPLY_ID);
+
+        // Should dispatch if expected error and execution failed
+        contract.call_remote_error(true).call(&owner).unwrap();
+        let last_reply = contract.last_reply().unwrap();
+        assert_eq!(last_reply, ERROR_REPLY_ID);
+
+        // Should dispatch if expected any result and execution succeeded
+        contract.call_remote_always(false).call(&owner).unwrap();
+        let last_reply = contract.last_reply().unwrap();
+        assert_eq!(last_reply, ALWAYS_REPLY_ID);
+
+        // Should dispatch if expected both results and execution succeeded
+        contract.call_remote_both(false).call(&owner).unwrap();
+        let last_reply = contract.last_reply().unwrap();
+        assert_eq!(last_reply, SUCCESS_REPLY_ID);
+
+        // Should dispatch if expected any result and execution failed
+        contract.call_remote_always(true).call(&owner).unwrap();
+        let last_reply = contract.last_reply().unwrap();
+        assert_eq!(last_reply, ALWAYS_REPLY_ID);
+
+        // Should dispatch if expected both results and execution failed
+        contract.call_remote_both(true).call(&owner).unwrap();
+        let last_reply = contract.last_reply().unwrap();
+        assert_eq!(last_reply, ERROR_REPLY_ID);
+
+        // Should send the cosmos message
+        contract.send_cosmos_messages().call(&owner).unwrap();
+        let last_reply = contract.last_reply().unwrap();
+        assert_eq!(last_reply, ALWAYS_REPLY_ID);
+
+        // Should return error if unknown reply ID received
+        let unknown_reply_id = 42u64;
+        let err = contract
+            .call_remote_unknown_id(false, unknown_reply_id)
+            .call(&owner)
+            .unwrap_err();
+        assert_eq!(
+            err,
+            StdError::generic_err(format!("Unknown reply id: {}.", unknown_reply_id)).into()
+        );
+    }
+}
diff --git a/sylvia/tests/reply_generation.rs b/sylvia/tests/reply_generation.rs
new file mode 100644
index 0000000..83a95fd
--- /dev/null
+++ b/sylvia/tests/reply_generation.rs
@@ -0,0 +1,99 @@
+use cosmwasm_std::{Binary, SubMsgResult};
+use itertools::Itertools;
+use sylvia::ctx::InstantiateCtx;
+use sylvia::ctx::ReplyCtx;
+use sylvia::cw_std::{Response, StdResult};
+use sylvia::{contract, entry_points};
+
+pub struct Contract;
+
+#[entry_points]
+#[contract]
+#[sv::features(replies)]
+impl Contract {
+    pub fn new() -> Self {
+        Self
+    }
+
+    #[sv::msg(instantiate)]
+    pub fn instantiate(&self, _ctx: InstantiateCtx) -> StdResult<Response> {
+        Ok(Response::new())
+    }
+
+    #[sv::msg(reply)]
+    fn clean(
+        &self,
+        _ctx: ReplyCtx,
+        _result: SubMsgResult,
+        #[sv::payload(raw)] _payload: Binary,
+    ) -> StdResult<Response> {
+        Ok(Response::new())
+    }
+
+    #[allow(dead_code)]
+    #[sv::msg(reply, handlers=[handler_one, handler_two])]
+    fn custom_handlers(
+        &self,
+        _ctx: ReplyCtx,
+        _result: SubMsgResult,
+        #[sv::payload(raw)] _payload: Binary,
+    ) -> StdResult<Response> {
+        Ok(Response::new())
+    }
+
+    #[allow(dead_code)]
+    #[sv::msg(reply, reply_on = success)]
+    fn two_handlers(
+        &self,
+        _ctx: ReplyCtx,
+        #[sv::data(raw, opt)] _data: Option<Binary>,
+        #[sv::payload(raw)] _payload: Binary,
+    ) -> StdResult<Response> {
+        Ok(Response::new())
+    }
+
+    #[allow(dead_code)]
+    #[sv::msg(reply, reply_on = always)]
+    fn reply_on_always(
+        &self,
+        _ctx: ReplyCtx,
+        _result: SubMsgResult,
+        #[sv::payload(raw)] _payload: Binary,
+    ) -> StdResult<Response> {
+        Ok(Response::new())
+    }
+
+    #[allow(dead_code)]
+    #[sv::msg(reply, handlers=[two_handlers], reply_on = error)]
+    fn both_parameters(
+        &self,
+        _ctx: ReplyCtx,
+        _error: String,
+        #[sv::payload(raw)] _payload: Binary,
+    ) -> StdResult<Response> {
+        Ok(Response::new())
+    }
+}
+
+#[test]
+fn reply_id_generation() {
+    // Assert IDs uniqueness
+    let unique_ids_count = [
+        sv::CLEAN_REPLY_ID,
+        sv::HANDLER_ONE_REPLY_ID,
+        sv::HANDLER_TWO_REPLY_ID,
+        sv::TWO_HANDLERS_REPLY_ID,
+        sv::REPLY_ON_ALWAYS_REPLY_ID,
+    ]
+    .iter()
+    .unique()
+    .count();
+
+    assert_eq!(unique_ids_count, 5);
+
+    assert_eq!(sv::CLEAN_REPLY_ID, 0);
+    assert_eq!(sv::HANDLER_ONE_REPLY_ID, 1);
+    assert_eq!(sv::HANDLER_TWO_REPLY_ID, 2);
+    assert_eq!(sv::TWO_HANDLERS_REPLY_ID, 3);
+    assert_eq!(sv::REPLY_ON_ALWAYS_REPLY_ID, 4);
+}
diff --git a/sylvia/tests/ui/attributes/attr_forwarding/invalid_sv_attr.rs b/sylvia/tests/ui/attributes/attr_forwarding/invalid_sv_attr.rs
index 34db261..3dfb3a2 100644
--- a/sylvia/tests/ui/attributes/attr_forwarding/invalid_sv_attr.rs
+++ b/sylvia/tests/ui/attributes/attr_forwarding/invalid_sv_attr.rs
@@ -1,11 +1,11 @@
 #![allow(unused_imports)]
+use sylvia::ctx::{ExecCtx, InstantiateCtx};
 use sylvia::cw_std::{Response, StdResult};
-use sylvia::types::{ExecCtx, InstantiateCtx};
 
 pub struct Contract;
 
 #[sylvia::contract]
-#[sv::msg_attr(exec,)]
+#[sv::msg_attr(exec)]
 #[sv::msg_attr(random_msg, PartialOrd)]
 #[sv::msg_attr(exec PartialOrd)]
 #[sv::msg_attr(PartialOrd)]
diff --git a/sylvia/tests/ui/attributes/attr_forwarding/invalid_sv_attr.stderr b/sylvia/tests/ui/attributes/attr_forwarding/invalid_sv_attr.stderr
index 6dda730..9fd27b6 100644
--- a/sylvia/tests/ui/attributes/attr_forwarding/invalid_sv_attr.stderr
+++ b/sylvia/tests/ui/attributes/attr_forwarding/invalid_sv_attr.stderr
@@ -1,7 +1,7 @@
 error: Expected attribute of the form: `#[sv::msg_attr(msg_type, attribute_to_forward)]`
  --> tests/ui/attributes/attr_forwarding/invalid_sv_attr.rs:8:16
   |
-8 | #[sv::msg_attr(exec,)]
+8 | #[sv::msg_attr(exec)]
   |                ^^^^
 
 error: Invalid message type, expected one of: `exec`, `query`, `instantiate`, `migrate`, `reply` or `sudo`.
diff --git a/sylvia/tests/ui/attributes/custom/invalid_custom.rs b/sylvia/tests/ui/attributes/custom/invalid_custom.rs
index 0d33699..5131635 100644
--- a/sylvia/tests/ui/attributes/custom/invalid_custom.rs
+++ b/sylvia/tests/ui/attributes/custom/invalid_custom.rs
@@ -1,6 +1,6 @@
 #![allow(unused_imports)]
+use sylvia::ctx::InstantiateCtx;
 use sylvia::cw_std::{Response, StdResult};
-use sylvia::types::InstantiateCtx;
 
 #[sylvia::cw_schema::cw_serde]
 pub struct MyMsg;
diff --git a/sylvia/tests/ui/attributes/data/invalid_params.rs b/sylvia/tests/ui/attributes/data/invalid_params.rs
new file mode 100644
index 0000000..170e277
--- /dev/null
+++ b/sylvia/tests/ui/attributes/data/invalid_params.rs
@@ -0,0 +1,65 @@
+#![allow(unused_imports)]
+
+use sylvia::contract;
+use sylvia::ctx::{InstantiateCtx, ReplyCtx};
+use sylvia::cw_std::{Binary, Reply, Response, StdResult};
+
+pub mod invalid_data {
+    use super::*;
+
+    pub struct Contract;
+
+    #[contract]
+    #[sv::features(replies)]
+    impl Contract {
+        pub fn new() -> Self {
+            Self
+        }
+
+        #[sv::msg(instantiate)]
+        pub fn instantiate(&self, _ctx: InstantiateCtx) -> StdResult<Response> {
+            Ok(Response::new())
+        }
+
+        #[sv::msg(reply, reply_on=success)]
+        fn reply(
+            &self,
+            _ctx: ReplyCtx,
+            #[sv::data(invalid)] _data: Option<Binary>,
+            _param: String,
+        ) -> StdResult<Response> {
+            Ok(Response::new())
+        }
+    }
+}
+
+pub mod instantiate_and_raw {
+    use super::*;
+
+    pub struct Contract;
+
+    #[contract]
+    #[sv::features(replies)]
+    impl Contract {
+        pub fn new() -> Self {
+            Self
+        }
+
+        #[sv::msg(instantiate)]
+        pub fn instantiate(&self, _ctx: InstantiateCtx) -> StdResult<Response> {
+            Ok(Response::new())
+        }
+
+        #[sv::msg(reply, reply_on=success)]
+        fn reply(
+            &self,
+            _ctx: ReplyCtx,
+            #[sv::data(instantiate, raw)] _data: Option<Binary>,
+            _param: String,
+        ) -> StdResult<Response> {
+            Ok(Response::new())
+        }
+    }
+}
+
+fn main() {}
diff --git a/sylvia/tests/ui/attributes/data/invalid_params.stderr b/sylvia/tests/ui/attributes/data/invalid_params.stderr
new file mode 100644
index 0000000..47d4efe
--- /dev/null
+++ b/sylvia/tests/ui/attributes/data/invalid_params.stderr
@@ -0,0 +1,16 @@
+error: Invalid data parameter.
+
+         = note: Expected one of [`raw`, `opt`, `instantiate`] comma separated.
+  --> tests/ui/attributes/data/invalid_params.rs:28:24
+   |
+28 |             #[sv::data(invalid)] _data: Option<Binary>,
+   |                        ^^^^^^^
+
+error: The `instantiate` cannot be used in pair with `raw` parameter.
+
+         = note: Use any combination of [`raw`, `opt`] or [`instantiate`, `opt`] pairs.
+
+  --> tests/ui/attributes/data/invalid_params.rs:57:24
+   |
+57 |             #[sv::data(instantiate, raw)] _data: Option<Binary>,
+   |                        ^^^^^^^^^^^
diff --git a/sylvia/tests/ui/attributes/data/invalid_usage.rs b/sylvia/tests/ui/attributes/data/invalid_usage.rs
new file mode 100644
index 0000000..92ca86b
--- /dev/null
+++ b/sylvia/tests/ui/attributes/data/invalid_usage.rs
@@ -0,0 +1,63 @@
+#![allow(unused_imports, unused_variables)]
+use sylvia::ctx::{InstantiateCtx, ReplyCtx};
+use sylvia::cw_std::{Addr, Binary, Response, StdResult};
+
+pub mod attributes_swapped {
+    use super::*;
+
+    pub struct Contract {}
+
+    #[sylvia::contract]
+    #[sv::features(replies)]
+    impl Contract {
+        pub const fn new() -> Self {
+            Self {}
+        }
+
+        #[sv::msg(instantiate)]
+        fn instantiate(&self, _ctx: InstantiateCtx) -> StdResult<Response> {
+            Ok(Response::new())
+        }
+
+        #[sv::msg(reply, reply_on=success)]
+        fn reply(
+            &self,
+            _ctx: ReplyCtx,
+            #[sv::payload(raw)] param: Binary,
+            #[sv::data(opt, raw)] _data: Option<Binary>,
+        ) -> StdResult<Response> {
+            Ok(Response::new())
+        }
+    }
+}
+
+pub mod error_handler {
+    use super::*;
+
+    pub struct Contract {}
+
+    #[sylvia::contract]
+    #[sv::features(replies)]
+    impl Contract {
+        pub const fn new() -> Self {
+            Self {}
+        }
+
+        #[sv::msg(instantiate)]
+        fn instantiate(&self, _ctx: InstantiateCtx) -> StdResult<Response> {
+            Ok(Response::new())
+        }
+
+        #[sv::msg(reply, reply_on=error)]
+        fn reply(
+            &self,
+            _ctx: ReplyCtx,
+            #[sv::data(opt, raw)] _data: Option<Binary>,
+            #[sv::payload(raw)] param: Binary,
+        ) -> StdResult<Response> {
+            Ok(Response::new())
+        }
+    }
+}
+
+fn main() {}
diff --git a/sylvia/tests/ui/attributes/data/invalid_usage.stderr b/sylvia/tests/ui/attributes/data/invalid_usage.stderr
new file mode 100644
index 0000000..fd6884a
--- /dev/null
+++ b/sylvia/tests/ui/attributes/data/invalid_usage.stderr
@@ -0,0 +1,27 @@
+error: Wrong usage of `#[sv::data]` attribute.
+
+         = note: The `#[sv::data]` attribute can only be used on the first parameter after the `ReplyCtx`.
+
+  --> tests/ui/attributes/data/invalid_usage.rs:27:35
+   |
+27 |             #[sv::data(opt, raw)] _data: Option<Binary>,
+   |                                   ^^^^^
+
+error: Redundant payload parameter.
+
+         = note: Expected no parameters after the parameter marked with `#[sv::payload(raw)]`.
+
+  --> tests/ui/attributes/data/invalid_usage.rs:27:35
+   |
+27 |             #[sv::data(opt, raw)] _data: Option<Binary>,
+   |                                   ^^^^^
+
+error: Wrong usage of `#[sv::data]` attribute.
+
+         = note: The `#[sv::data]` attribute can only be used in `success` scenario.
+         = note: Found usage in `error` scenario.
+
+  --> tests/ui/attributes/data/invalid_usage.rs:55:35
+   |
+55 |             #[sv::data(opt, raw)] _data: Option<Binary>,
+   |                                   ^^^^^
diff --git a/sylvia/tests/ui/attributes/features/invalid_params.rs b/sylvia/tests/ui/attributes/features/invalid_params.rs
new file mode 100644
index 0000000..561405e
--- /dev/null
+++ b/sylvia/tests/ui/attributes/features/invalid_params.rs
@@ -0,0 +1,21 @@
+#![allow(unused_imports)]
+use sylvia::contract;
+use sylvia::ctx::InstantiateCtx;
+use sylvia::cw_std::{Response, StdResult};
+
+pub struct Contract;
+
+#[contract]
+#[sv::features(unknown_parameter)]
+impl Contract {
+    pub fn new() -> Self {
+        Self
+    }
+
+    #[sv::msg(instantiate)]
+    pub fn instantiate(&self, _ctx: InstantiateCtx) -> StdResult<Response> {
+        Ok(Response::new())
+    }
+}
+
+fn main() {}
diff --git a/sylvia/tests/ui/attributes/features/invalid_params.stderr b/sylvia/tests/ui/attributes/features/invalid_params.stderr
new file mode 100644
index 0000000..8178d72
--- /dev/null
+++ b/sylvia/tests/ui/attributes/features/invalid_params.stderr
@@ -0,0 +1,6 @@
+error: Invalid feature.
+       = note: Supported features for contract macro: [`replies`].
+ --> tests/ui/attributes/features/invalid_params.rs:9:16
+  |
+9 | #[sv::features(unknown_parameter)]
+  |                ^^^^^^^^^^^^^^^^^
diff --git a/sylvia/tests/ui/attributes/messages/invalid_custom.rs b/sylvia/tests/ui/attributes/messages/invalid_custom.rs
index 5c4703a..b8df374 100644
--- a/sylvia/tests/ui/attributes/messages/invalid_custom.rs
+++ b/sylvia/tests/ui/attributes/messages/invalid_custom.rs
@@ -1,6 +1,6 @@
 #![allow(unused_imports)]
+use sylvia::ctx::InstantiateCtx;
 use sylvia::cw_std::{Response, StdResult};
-use sylvia::types::InstantiateCtx;
 
 mod interface {
     use sylvia::cw_std::StdError;
diff --git a/sylvia/tests/ui/attributes/messages/unexpected_token.rs b/sylvia/tests/ui/attributes/messages/unexpected_token.rs
index 7d5cbad..ff0bd61 100644
--- a/sylvia/tests/ui/attributes/messages/unexpected_token.rs
+++ b/sylvia/tests/ui/attributes/messages/unexpected_token.rs
@@ -1,6 +1,6 @@
 #![allow(unused_imports)]
+use sylvia::ctx::InstantiateCtx;
 use sylvia::cw_std::{Empty, Response, StdResult};
-use sylvia::types::InstantiateCtx;
 
 mod interface {
     use sylvia::cw_std::{Empty, StdError};
diff --git a/sylvia/tests/ui/attributes/msg/invalid_params.rs b/sylvia/tests/ui/attributes/msg/invalid_params.rs
new file mode 100644
index 0000000..7f9de50
--- /dev/null
+++ b/sylvia/tests/ui/attributes/msg/invalid_params.rs
@@ -0,0 +1,25 @@
+#![allow(unused_imports)]
+use sylvia::contract;
+use sylvia::ctx::{InstantiateCtx, ReplyCtx};
+use sylvia::cw_std::{Reply, Response, StdResult};
+
+pub struct Contract;
+
+#[contract]
+impl Contract {
+    pub fn new() -> Self {
+        Self
+    }
+
+    #[sv::msg(instantiate)]
+    pub fn instantiate(&self, _ctx: InstantiateCtx) -> StdResult<Response> {
+        Ok(Response::new())
+    }
+
+    #[sv::msg(reply, unknown_parameter)]
+    fn reply(&self, _ctx: ReplyCtx, _reply: Reply) -> StdResult<Response> {
+        Ok(Response::new())
+    }
+}
+
+fn main() {}
diff --git a/sylvia/tests/ui/attributes/msg/invalid_params.stderr b/sylvia/tests/ui/attributes/msg/invalid_params.stderr
new file mode 100644
index 0000000..a7b6c5b
--- /dev/null
+++ b/sylvia/tests/ui/attributes/msg/invalid_params.stderr
@@ -0,0 +1,5 @@
+error: Invalid argument type, expected `resp`, `handlers`, `reply_on` or no argument.
+  --> tests/ui/attributes/msg/invalid_params.rs:19:22
+   |
+19 |     #[sv::msg(reply, unknown_parameter)]
+   |                      ^^^^^^^^^^^^^^^^^
diff --git a/sylvia/tests/ui/attributes/msg/overlapping_reply_handlers.rs b/sylvia/tests/ui/attributes/msg/overlapping_reply_handlers.rs
new file mode 100644
index 0000000..f19b710
--- /dev/null
+++ b/sylvia/tests/ui/attributes/msg/overlapping_reply_handlers.rs
@@ -0,0 +1,61 @@
+#![allow(unused_imports)]
+
+use sylvia::contract;
+use sylvia::ctx::{InstantiateCtx, ReplyCtx};
+use sylvia::cw_std::{Reply, Response, StdResult};
+
+pub struct Contract;
+
+#[contract]
+#[sv::features(replies)]
+impl Contract {
+    pub fn new() -> Self {
+        Self
+    }
+
+    #[sv::msg(instantiate)]
+    pub fn instantiate(&self, _ctx: InstantiateCtx) -> StdResult<Response> {
+        Ok(Response::new())
+    }
+
+    #[sv::msg(reply, handlers=[handler1])]
+    fn reply_always(
+        &self,
+        _ctx: ReplyCtx,
+        _result: SubMsgResult,
+        #[sv::payload(raw)] payload: Binary,
+    ) -> StdResult<Response> {
+        Ok(Response::new())
+    }
+
+    #[sv::msg(reply, handlers=[handler1], reply_on=success)]
+    fn duplicated_success_for_reply_always(
+        &self,
+        _ctx: ReplyCtx,
+        #[sv::payload(raw)] reply: Binary,
+    ) -> StdResult<Response> {
+        Ok(Response::new())
+    }
+
+    #[sv::msg(reply, handlers=[handler2], reply_on=error)]
+    fn some_reply(
+        &self,
+        _ctx: ReplyCtx,
+        error: String,
+        #[sv::payload(raw)] payload: Binary,
+    ) -> StdResult<Response> {
+        Ok(Response::new())
+    }
+
+    #[sv::msg(reply, reply_on=error)]
+    fn handler2(
+        &self,
+        _ctx: ReplyCtx,
+        error: String,
+        #[sv::payload(raw)] payload: Binary,
+    ) -> StdResult<Response> {
+        Ok(Response::new())
+    }
+}
+
+fn main() {}
diff --git a/sylvia/tests/ui/attributes/msg/overlapping_reply_handlers.stderr b/sylvia/tests/ui/attributes/msg/overlapping_reply_handlers.stderr
new file mode 100644
index 0000000..a452d77
--- /dev/null
+++ b/sylvia/tests/ui/attributes/msg/overlapping_reply_handlers.stderr
@@ -0,0 +1,17 @@
+error: Duplicated reply handler.
+
+         = note: Previous definition of handler=`HANDLER_1_REPLY_ID` for reply_on=`always` defined on `fn reply_always()`
+
+  --> tests/ui/attributes/msg/overlapping_reply_handlers.rs:31:32
+   |
+31 |     #[sv::msg(reply, handlers=[handler1], reply_on=success)]
+   |                                ^^^^^^^^
+
+error: Duplicated reply handler.
+
+         = note: Previous definition of handler=`HANDLER_2_REPLY_ID` for reply_on=`error` defined on `fn some_reply()`
+
+  --> tests/ui/attributes/msg/overlapping_reply_handlers.rs:51:8
+   |
+51 |     fn handler2(
+   |        ^^^^^^^^
diff --git a/sylvia/tests/ui/attributes/payload/invalid_params.rs b/sylvia/tests/ui/attributes/payload/invalid_params.rs
new file mode 100644
index 0000000..8efd89a
--- /dev/null
+++ b/sylvia/tests/ui/attributes/payload/invalid_params.rs
@@ -0,0 +1,32 @@
+#![allow(unused_imports)]
+
+use sylvia::contract;
+use sylvia::ctx::{InstantiateCtx, ReplyCtx};
+use sylvia::cw_std::{Binary, Reply, Response, StdResult};
+
+pub struct Contract;
+
+#[contract]
+#[sv::features(replies)]
+impl Contract {
+    pub fn new() -> Self {
+        Self
+    }
+
+    #[sv::msg(instantiate)]
+    pub fn instantiate(&self, _ctx: InstantiateCtx) -> StdResult<Response> {
+        Ok(Response::new())
+    }
+
+    #[sv::msg(reply, reply_on=success)]
+    fn reply(
+        &self,
+        _ctx: ReplyCtx,
+        #[sv::data(raw, opt)] _data: Option<Binary>,
+        #[sv::payload(invalid)] _param: Option<Binary>,
+    ) -> StdResult<Response> {
+        Ok(Response::new())
+    }
+}
+
+fn main() {}
diff --git a/sylvia/tests/ui/attributes/payload/invalid_params.stderr b/sylvia/tests/ui/attributes/payload/invalid_params.stderr
new file mode 100644
index 0000000..0705ccd
--- /dev/null
+++ b/sylvia/tests/ui/attributes/payload/invalid_params.stderr
@@ -0,0 +1,6 @@
+error: Invalid payload parameter.
+       = note: Expected [`raw`].
+  --> tests/ui/attributes/payload/invalid_params.rs:26:23
+   |
+26 |         #[sv::payload(invalid)] _param: Option<Binary>,
+   |                       ^^^^^^^
diff --git a/sylvia/tests/ui/attributes/payload/invalid_usage.rs b/sylvia/tests/ui/attributes/payload/invalid_usage.rs
new file mode 100644
index 0000000..1946aa2
--- /dev/null
+++ b/sylvia/tests/ui/attributes/payload/invalid_usage.rs
@@ -0,0 +1,57 @@
+#![allow(unused_imports, unused_variables)]
+use sylvia::ctx::{InstantiateCtx, ReplyCtx};
+use sylvia::cw_std::{Addr, Binary, Response, StdResult};
+
+pub mod used_on_context {
+    use super::*;
+
+    pub struct Contract {}
+
+    #[sylvia::contract]
+    #[sv::features(replies)]
+    impl Contract {
+        pub const fn new() -> Self {
+            Self {}
+        }
+
+        #[sv::msg(instantiate)]
+        fn instantiate(&self, _ctx: InstantiateCtx) -> StdResult<Response> {
+            Ok(Response::new())
+        }
+
+        #[sv::msg(reply, reply_on=success)]
+        fn reply(&self, #[sv::payload(raw)] _ctx: ReplyCtx) -> StdResult<Response> {
+            Ok(Response::new())
+        }
+    }
+}
+
+pub mod used_on_self {
+    use super::*;
+
+    pub struct Contract {}
+
+    #[sylvia::contract]
+    #[sv::features(replies)]
+    impl Contract {
+        pub const fn new() -> Self {
+            Self {}
+        }
+
+        #[sv::msg(instantiate)]
+        fn instantiate(&self, _ctx: InstantiateCtx) -> StdResult<Response> {
+            Ok(Response::new())
+        }
+
+        #[sv::msg(reply, reply_on=success)]
+        fn reply(
+            #[sv::payload(raw)] &self,
+            _ctx: ReplyCtx,
+            payload: Binary,
+        ) -> StdResult<Response> {
+            Ok(Response::new())
+        }
+    }
+}
+
+fn main() {}
diff --git a/sylvia/tests/ui/attributes/payload/invalid_usage.stderr b/sylvia/tests/ui/attributes/payload/invalid_usage.stderr
new file mode 100644
index 0000000..5b67050
--- /dev/null
+++ b/sylvia/tests/ui/attributes/payload/invalid_usage.stderr
@@ -0,0 +1,28 @@
+error: Invalid usage of Sylvia attribute.
+
+         = note: First and second arguments of the method should be `self` and `ctx` respectively.
+         = note: Unexpected attribute on `ctx` parameter.
+
+  --> tests/ui/attributes/payload/invalid_usage.rs:23:25
+   |
+23 |         fn reply(&self, #[sv::payload(raw)] _ctx: ReplyCtx) -> StdResult<Response> {
+   |                         ^
+
+error: Missing payload parameter.
+
+         = note: Expected at least one payload parameter at the end of parameter list.
+
+  --> tests/ui/attributes/payload/invalid_usage.rs:23:12
+   |
+23 |         fn reply(&self, #[sv::payload(raw)] _ctx: ReplyCtx) -> StdResult<Response> {
+   |            ^^^^^
+
+error: Invalid usage of Sylvia attribute.
+
+         = note: First and second arguments of the method should be `self` and `ctx` respectively.
+         = note: Unexpected attribute on `self` parameter.
+
+  --> tests/ui/attributes/payload/invalid_usage.rs:48:13
+   |
+48 |             #[sv::payload(raw)] &self,
+   |             ^
diff --git a/sylvia/tests/ui/macros/entry_points.rs b/sylvia/tests/ui/macros/entry_points.rs
index 84a1e9a..24749c3 100644
--- a/sylvia/tests/ui/macros/entry_points.rs
+++ b/sylvia/tests/ui/macros/entry_points.rs
@@ -1,7 +1,8 @@
 #![allow(unused_imports)]
 
+use sylvia::ctx::InstantiateCtx;
 use sylvia::cw_std::{Response, StdResult};
-use sylvia::types::{CustomMsg, CustomQuery, InstantiateCtx};
+use sylvia::types::{CustomMsg, CustomQuery};
 use sylvia::{contract, entry_points};
 
 pub mod no_generics {
diff --git a/sylvia/tests/ui/macros/entry_points.stderr b/sylvia/tests/ui/macros/entry_points.stderr
index 7548a3d..54033c2 100644
--- a/sylvia/tests/ui/macros/entry_points.stderr
+++ b/sylvia/tests/ui/macros/entry_points.stderr
@@ -2,9 +2,9 @@ error: Missing concrete types.
 
          = note: For every generic type in the contract, a concrete type must be provided in `#[entry_points(generics<T1, T2, ...>)]`.
 
-  --> tests/ui/macros/entry_points.rs:14:5
+  --> tests/ui/macros/entry_points.rs:15:5
    |
-14 |     #[entry_points]
+15 |     #[entry_points]
    |     ^^^^^^^^^^^^^^^
    |
    = note: this error originates in the attribute macro `entry_points` (in Nightly builds, run with -Z macro-backtrace for more info)
@@ -13,9 +13,9 @@ error: Missing concrete types.
 
          = note: For every generic type in the contract, a concrete type must be provided in `#[entry_points(generics<T1, T2, ...>)]`.
 
-  --> tests/ui/macros/entry_points.rs:42:20
+  --> tests/ui/macros/entry_points.rs:43:20
    |
-42 |     #[entry_points(generics<Empty>)]
+43 |     #[entry_points(generics<Empty>)]
    |                    ^^^^^^^^
 
 error: Missing instantiation message.
@@ -23,32 +23,32 @@ error: Missing instantiation message.
          = note: `sylvia::entry_points` requires exactly one method marked with `#[sv::msg(instantiation)]` attribute.
          = note: Make sure you implemented the `entry_points` macro above the `contract` macro.
 
-  --> tests/ui/macros/entry_points.rs:69:5
+  --> tests/ui/macros/entry_points.rs:70:5
    |
-69 |     #[entry_points]
+70 |     #[entry_points]
    |     ^^^^^^^^^^^^^^^
    |
    = note: this error originates in the attribute macro `entry_points` (in Nightly builds, run with -Z macro-backtrace for more info)
 
 error[E0599]: no method named `instantiate` found for reference `&wrong_order::Contract` in the current scope
-  --> tests/ui/macros/entry_points.rs:76:12
+  --> tests/ui/macros/entry_points.rs:77:12
    |
-76 |         fn instantiate(&self, _ctx: InstantiateCtx) -> StdResult<Response> {
+77 |         fn instantiate(&self, _ctx: InstantiateCtx) -> StdResult<Response> {
    |            ^^^^^^^^^^^ method not found in `&Contract`
    |
    = help: items from traits can only be used if the trait is in scope
 help: trait `Contract` which provides `instantiate` is implemented but not in scope; perhaps you want to import it
    |
-68 +     use sylvia::cw_multi_test::Contract;
+69 +     use sylvia::cw_multi_test::Contract;
    |
 
 error[E0599]: no function or associated item named `new` found for struct `wrong_order::Contract` in the current scope
-  --> tests/ui/macros/entry_points.rs:68:5
+  --> tests/ui/macros/entry_points.rs:69:5
    |
-66 |     pub struct Contract;
+67 |     pub struct Contract;
    |     ------------------- function or associated item `new` not found for this struct
-67 |
-68 |     #[contract]
+68 |
+69 |     #[contract]
    |     ^^^^^^^^^^^ function or associated item not found in `Contract`
    |
    = help: items from traits can only be used if the trait is implemented and in scope
diff --git a/sylvia/tests/ui/method_signature/reply.rs b/sylvia/tests/ui/method_signature/reply.rs
new file mode 100644
index 0000000..04dc6dd
--- /dev/null
+++ b/sylvia/tests/ui/method_signature/reply.rs
@@ -0,0 +1,135 @@
+#![allow(unused_imports, unused_variables)]
+use sylvia::ctx::{InstantiateCtx, ReplyCtx};
+use sylvia::cw_std::{Addr, Binary, Response, StdResult};
+
+pub mod mismatched_params {
+    use super::*;
+
+    pub struct Contract {}
+
+    #[sylvia::contract]
+    #[sv::features(replies)]
+    impl Contract {
+        pub const fn new() -> Self {
+            Self {}
+        }
+
+        #[sv::msg(instantiate)]
+        fn instantiate(&self, _ctx: InstantiateCtx) -> StdResult<Response> {
+            Ok(Response::new())
+        }
+
+        #[sv::msg(reply, handlers=[on_instantiated], reply_on=success)]
+        fn first_reply(
+            &self,
+            _ctx: ReplyCtx,
+            #[sv::data(opt, raw)] _data: Option<Binary>,
+            param: String,
+        ) -> StdResult<Response> {
+            Ok(Response::new())
+        }
+
+        #[sv::msg(reply, handlers=[on_instantiated], reply_on=error)]
+        fn second_reply(&self, _ctx: ReplyCtx, error: String, param: u32) -> StdResult<Response> {
+            Ok(Response::new())
+        }
+    }
+}
+
+pub mod mismatched_param_arity {
+    use super::*;
+
+    pub struct Contract {}
+
+    #[sylvia::contract]
+    #[sv::features(replies)]
+    impl Contract {
+        pub const fn new() -> Self {
+            Self {}
+        }
+
+        #[sv::msg(instantiate)]
+        fn instantiate(&self, _ctx: InstantiateCtx) -> StdResult<Response> {
+            Ok(Response::new())
+        }
+
+        #[sv::msg(reply, handlers=[on_instantiated], reply_on=success)]
+        fn first_reply(
+            &self,
+            _ctx: ReplyCtx,
+            #[sv::data(opt, raw)] _data: Option<Binary>,
+            param: String,
+        ) -> StdResult<Response> {
+            Ok(Response::new())
+        }
+
+        #[sv::msg(reply, handlers=[on_instantiated], reply_on=error)]
+        fn second_reply(
+            &self,
+            _ctx: ReplyCtx,
+            error: String,
+            param: String,
+            param: u32,
+        ) -> StdResult<Response> {
+            Ok(Response::new())
+        }
+    }
+}
+
+pub mod redundant_params {
+    use super::*;
+
+    pub struct Contract {}
+
+    #[sylvia::contract]
+    #[sv::features(replies)]
+    impl Contract {
+        pub const fn new() -> Self {
+            Self {}
+        }
+
+        #[sv::msg(instantiate)]
+        fn instantiate(&self, _ctx: InstantiateCtx) -> StdResult<Response> {
+            Ok(Response::new())
+        }
+
+        #[sv::msg(reply, reply_on=success)]
+        fn first_reply(
+            &self,
+            _ctx: ReplyCtx,
+            redundant_before1: u32,
+            redundant_before2: String,
+            #[sv::data(opt, raw)] _data: Option<Binary>,
+            #[sv::payload(raw)] param: String,
+        ) -> StdResult<Response> {
+            Ok(Response::new())
+        }
+
+        #[sv::msg(reply, reply_on=success)]
+        fn second_reply(
+            &self,
+            _ctx: ReplyCtx,
+            #[sv::data(opt, raw)] _data: Option<Binary>,
+            redundant_between1: u32,
+            redudnant_between2: String,
+            #[sv::payload(raw)] param: String,
+            redundant_after1: Binary,
+            redundant_after2: Addr,
+        ) -> StdResult<Response> {
+            Ok(Response::new())
+        }
+
+        #[sv::msg(reply, reply_on=success)]
+        fn third_reply(
+            &self,
+            _ctx: ReplyCtx,
+            #[sv::data(opt, raw)] _data: Option<Binary>,
+            #[sv::payload(raw)] param: String,
+            redundant: Binary,
+        ) -> StdResult<Response> {
+            Ok(Response::new())
+        }
+    }
+}
+
+fn main() {}
diff --git a/sylvia/tests/ui/method_signature/reply.stderr b/sylvia/tests/ui/method_signature/reply.stderr
new file mode 100644
index 0000000..2aad722
--- /dev/null
+++ b/sylvia/tests/ui/method_signature/reply.stderr
@@ -0,0 +1,55 @@
+error: Mismatched parameter in reply handlers.
+
+         = note: Parameters for the `on_instantiated` handler have to be the same.
+         = note: Previous parameter defined for the `on_instantiated` handler.
+
+  --> tests/ui/method_signature/reply.rs:27:13
+   |
+27 |             param: String,
+   |             ^^^^^
+
+error: Mismatched quantity of method parameters.
+
+         = note: Both `on_instantiated` handlers should have the same number of parameters.
+         = note: Previous definition of on_instantiated handler.
+
+  --> tests/ui/method_signature/reply.rs:57:12
+   |
+57 |         fn first_reply(
+   |            ^^^^^^^^^^^
+
+error: Wrong usage of `#[sv::data]` attribute.
+
+         = note: The `#[sv::data]` attribute can only be used on the first parameter after the `ReplyCtx`.
+
+   --> tests/ui/method_signature/reply.rs:102:35
+    |
+102 |             #[sv::data(opt, raw)] _data: Option<Binary>,
+    |                                   ^^^^^
+
+error: Redundant payload parameter.
+
+         = note: Expected no parameters between the parameter marked with `#[sv::data]` and `#[sv::payload(raw)]`.
+
+   --> tests/ui/method_signature/reply.rs:100:13
+    |
+100 |             redundant_before1: u32,
+    |             ^^^^^^^^^^^^^^^^^
+
+error: Redundant payload parameter.
+
+         = note: Expected no parameters between the parameter marked with `#[sv::data]` and `#[sv::payload(raw)]`.
+
+   --> tests/ui/method_signature/reply.rs:113:13
+    |
+113 |             redundant_between1: u32,
+    |             ^^^^^^^^^^^^^^^^^^
+
+error: Redundant payload parameter.
+
+         = note: Expected no parameters after the parameter marked with `#[sv::payload(raw)]`.
+
+   --> tests/ui/method_signature/reply.rs:128:13
+    |
+128 |             redundant: Binary,
+    |             ^^^^^^^^^
diff --git a/sylvia/tests/ui/missing_method/instantiation.rs b/sylvia/tests/ui/missing_method/instantiation.rs
index 12aff85..594a6b1 100644
--- a/sylvia/tests/ui/missing_method/instantiation.rs
+++ b/sylvia/tests/ui/missing_method/instantiation.rs
@@ -1,3 +1,4 @@
+#![allow(unused_imports)]
 pub struct Contract {}
 
 #[sylvia::contract]
diff --git a/sylvia/tests/ui/missing_method/instantiation.stderr b/sylvia/tests/ui/missing_method/instantiation.stderr
index 77352b9..a20e8a4 100644
--- a/sylvia/tests/ui/missing_method/instantiation.stderr
+++ b/sylvia/tests/ui/missing_method/instantiation.stderr
@@ -2,7 +2,7 @@ error: Missing instantiation message.
 
          = note: `sylvia::contract` requires exactly one method marked with `#[sv::msg(instantiation)]` attribute.
 
- --> tests/ui/missing_method/instantiation.rs:4:1
+ --> tests/ui/missing_method/instantiation.rs:5:1
   |
-4 | impl Contract {
+5 | impl Contract {
   | ^^^^
diff --git a/sylvia/tests/ui/missing_method/msgs_misused.rs b/sylvia/tests/ui/missing_method/msgs_misused.rs
index 11572ce..94d9b13 100644
--- a/sylvia/tests/ui/missing_method/msgs_misused.rs
+++ b/sylvia/tests/ui/missing_method/msgs_misused.rs
@@ -1,13 +1,12 @@
 #![allow(unused_imports)]
+use sylvia::ctx::InstantiateCtx;
 use sylvia::cw_std::{Response, StdResult};
-use sylvia::types::InstantiateCtx;
 
 pub struct Contract;
 
-
 mod interface {
-    use sylvia::cw_std::{Response, StdResult, StdError};
-    use sylvia::types::{InstantiateCtx, MigrateCtx};
+    use sylvia::ctx::{InstantiateCtx, MigrateCtx};
+    use sylvia::cw_std::{Response, StdError, StdResult};
 
     #[sylvia::interface]
     trait Interface {
diff --git a/sylvia/tests/ui/missing_method/msgs_misused.stderr b/sylvia/tests/ui/missing_method/msgs_misused.stderr
index 0099269..709aabe 100644
--- a/sylvia/tests/ui/missing_method/msgs_misused.stderr
+++ b/sylvia/tests/ui/missing_method/msgs_misused.stderr
@@ -2,25 +2,25 @@ error: The message attribute `instantiate` is not supported in interfaces.
 
          = note: Contracts need to implement `instantiate` method within their `impl` block.
 
-  --> tests/ui/missing_method/msgs_misused.rs:17:12
+  --> tests/ui/missing_method/msgs_misused.rs:16:12
    |
-17 |         fn instantiate(&self, ctx: InstantiateCtx) -> StdResult<Response>;
+16 |         fn instantiate(&self, ctx: InstantiateCtx) -> StdResult<Response>;
    |            ^^^^^^^^^^^
 
 error: The message attribute `migrate` is not supported in interfaces
 
          = note: Contracts need to implement `migrate` method within their `impl` block.
 
-  --> tests/ui/missing_method/msgs_misused.rs:20:12
+  --> tests/ui/missing_method/msgs_misused.rs:19:12
    |
-20 |         fn migrate(&self, ctx: MigrateCtx) -> StdResult<Response>;
+19 |         fn migrate(&self, ctx: MigrateCtx) -> StdResult<Response>;
    |            ^^^^^^^
 
 error: More than one instantiation or migration message
 
          = note: Instantiation/Migration message previously defined here
 
-  --> tests/ui/missing_method/msgs_misused.rs:31:12
+  --> tests/ui/missing_method/msgs_misused.rs:30:12
    |
-31 |     pub fn instantiate(&self, ctx: InstantiateCtx) -> StdResult<Response> {
+30 |     pub fn instantiate(&self, ctx: InstantiateCtx) -> StdResult<Response> {
    |            ^^^^^^^^^^^
diff --git a/sylvia/tests/ui/missing_method/new.rs b/sylvia/tests/ui/missing_method/new.rs
index 2b21c26..d199815 100644
--- a/sylvia/tests/ui/missing_method/new.rs
+++ b/sylvia/tests/ui/missing_method/new.rs
@@ -1,6 +1,6 @@
 #![allow(unused_imports)]
+use sylvia::ctx::InstantiateCtx;
 use sylvia::cw_std::{Response, StdResult};
-use sylvia::types::InstantiateCtx;
 
 pub struct Contract {}
 
diff --git a/sylvia/tests/ui/missing_method/new_has_parameters.rs b/sylvia/tests/ui/missing_method/new_has_parameters.rs
index 43368f7..bb6836c 100644
--- a/sylvia/tests/ui/missing_method/new_has_parameters.rs
+++ b/sylvia/tests/ui/missing_method/new_has_parameters.rs
@@ -1,6 +1,6 @@
 #![allow(unused_imports)]
+use sylvia::ctx::InstantiateCtx;
 use sylvia::cw_std::{Response, StdResult};
-use sylvia::types::InstantiateCtx;
 
 pub struct Contract;