Repository: CosmWasm/sylvia
Pull request #436 (2024-09-06 16:15:22)

feat: Generate reply dispatch (#429)

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/CosmWasm/sylvia/pull/436?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 `82.80255%` with `27 lines` in your changes missing coverage. Please review.
> Project coverage is 71.06%. Comparing base [(`3105f0e`)](https://app.codecov.io/gh/CosmWasm/sylvia/commit/3105f0eeca25824cebd19a9803ef84c5c56c54c1?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm) to head [(`28914bc`)](https://app.codecov.io/gh/CosmWasm/sylvia/commit/28914bcac71a3536d8ab61752224410014578c7b?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/436?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/436?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==) | 93.91% | [7 Missing :warning: ](https://app.codecov.io/gh/CosmWasm/sylvia/pull/436?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm) |
| [sylvia-derive/src/contract/mt.rs](https://app.codecov.io/gh/CosmWasm/sylvia/pull/436?src=pr&el=tree&filepath=sylvia-derive%2Fsrc%2Fcontract%2Fmt.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm#diff-c3lsdmlhLWRlcml2ZS9zcmMvY29udHJhY3QvbXQucnM=) | 58.33% | [5 Missing :warning: ](https://app.codecov.io/gh/CosmWasm/sylvia/pull/436?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/436?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) | 58.33% | [5 Missing :warning: ](https://app.codecov.io/gh/CosmWasm/sylvia/pull/436?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm) |
| [sylvia/tests/reply.rs](https://app.codecov.io/gh/CosmWasm/sylvia/pull/436?src=pr&el=tree&filepath=sylvia%2Ftests%2Freply.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm#diff-c3lsdmlhL3Rlc3RzL3JlcGx5LnJz) | 0.00% | [5 Missing :warning: ](https://app.codecov.io/gh/CosmWasm/sylvia/pull/436?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/436?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% | [3 Missing :warning: ](https://app.codecov.io/gh/CosmWasm/sylvia/pull/436?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm) |
| [sylvia-derive/src/contract.rs](https://app.codecov.io/gh/CosmWasm/sylvia/pull/436?src=pr&el=tree&filepath=sylvia-derive%2Fsrc%2Fcontract.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm#diff-c3lsdmlhLWRlcml2ZS9zcmMvY29udHJhY3QucnM=) | 80.00% | [1 Missing :warning: ](https://app.codecov.io/gh/CosmWasm/sylvia/pull/436?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm) |
| [sylvia/src/types.rs](https://app.codecov.io/gh/CosmWasm/sylvia/pull/436?src=pr&el=tree&filepath=sylvia%2Fsrc%2Ftypes.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm#diff-c3lsdmlhL3NyYy90eXBlcy5ycw==) | 0.00% | [1 Missing :warning: ](https://app.codecov.io/gh/CosmWasm/sylvia/pull/436?src=pr&el=tree&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                         @@
##           jawoznia/feat/generate_reply_id     #436      +/-   ##
===================================================================
+ Coverage                            70.19%   71.06%   +0.87%     
===================================================================
  Files                                   55       53       -2     
  Lines                                 3157     3249      +92     
===================================================================
+ Hits                                  2216     2309      +93     
+ Misses                                 941      940       -1     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/CosmWasm/sylvia/pull/436?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).


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 9c44f98..2c27b17 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -22,9 +22,9 @@ checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
 
 [[package]]
 name = "anyhow"
-version = "1.0.86"
+version = "1.0.87"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
+checksum = "10f00e1f6e58a40e807377c75c6a7f97bf9044fab57816f2414e6f5f4499d7b8"
 
 [[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"
@@ -262,7 +262,7 @@ checksum = "029910b409398fdf81955d7301b906caf81f2c42b013ea074fbd89720229c424"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.75",
+ "syn 2.0.77",
 ]
 
 [[package]]
@@ -286,7 +286,7 @@ checksum = "edf5c8adac41bb7751c050d7c4c18675be19ee128714454454575e894424eeef"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.75",
+ "syn 2.0.77",
 ]
 
 [[package]]
@@ -314,9 +314,9 @@ dependencies = [
 
 [[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,7 +392,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.75",
+ "syn 2.0.77",
 ]
 
 [[package]]
@@ -477,7 +477,7 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.75",
+ "syn 2.0.77",
  "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]]
@@ -854,9 +854,9 @@ dependencies = [
 
 [[package]]
 name = "prost"
-version = "0.13.1"
+version = "0.13.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e13db3d3fde688c61e2446b4d843bc27a7e8af269a69440c0308021dc92333cc"
+checksum = "3b2ecbe40f08db5c006b5764a2645f7f3f141ce756412ac9e1dd6087e6d32995"
 dependencies = [
  "bytes",
  "prost-derive",
@@ -864,22 +864,22 @@ dependencies = [
 
 [[package]]
 name = "prost-derive"
-version = "0.13.1"
+version = "0.13.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "18bec9b0adc4eba778b33684b7ba3e7137789434769ee3ce3930463ef904cfca"
+checksum = "acf0c195eebb4af52c752bec4f52f645da98b6e92077a04110c7f349477ae5ac"
 dependencies = [
  "anyhow",
  "itertools 0.13.0",
  "proc-macro2",
  "quote",
- "syn 2.0.75",
+ "syn 2.0.77",
 ]
 
 [[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.77",
 ]
 
 [[package]]
@@ -1003,9 +1003,9 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
 
 [[package]]
 name = "serde"
-version = "1.0.208"
+version = "1.0.210"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2"
+checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
 dependencies = [
  "serde_derive",
 ]
@@ -1030,13 +1030,13 @@ dependencies = [
 
 [[package]]
 name = "serde_derive"
-version = "1.0.208"
+version = "1.0.210"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf"
+checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.75",
+ "syn 2.0.77",
 ]
 
 [[package]]
@@ -1047,14 +1047,14 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.75",
+ "syn 2.0.77",
 ]
 
 [[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",
@@ -1144,7 +1144,7 @@ dependencies = [
  "serde",
  "sylvia",
  "sylvia-runtime-macros",
- "syn 2.0.75",
+ "syn 2.0.77",
  "thiserror",
 ]
 
@@ -1172,9 +1172,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "2.0.75"
+version = "2.0.77"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9"
+checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1207,7 +1207,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.75",
+ "syn 2.0.77",
 ]
 
 [[package]]
@@ -1219,7 +1219,7 @@ dependencies = [
  "serde",
  "serde_spanned",
  "toml_datetime",
- "toml_edit 0.22.20",
+ "toml_edit",
 ]
 
 [[package]]
@@ -1231,17 +1231,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"
@@ -1252,7 +1241,7 @@ dependencies = [
  "serde",
  "serde_spanned",
  "toml_datetime",
- "winnow 0.6.18",
+ "winnow",
 ]
 
 [[package]]
@@ -1402,15 +1391,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"
@@ -1438,7 +1418,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.75",
+ "syn 2.0.77",
 ]
 
 [[package]]
@@ -1458,5 +1438,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.75",
+ "syn 2.0.77",
 ]
diff --git a/examples/Cargo.toml b/examples/Cargo.toml
index f99702b..b156b4a 100644
--- a/examples/Cargo.toml
+++ b/examples/Cargo.toml
@@ -27,6 +27,7 @@ version = "0.5.0"
 edition = "2021"
 
 [workspace.dependencies]
+sylvia = { path = "../sylvia", features = ["sv_replies"] }
 cw-storage-plus = "2.0.0"
 cw-utils = "2.0.0"
 cw2 = "2.0.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/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-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/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/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/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..663042f 100644
--- a/examples/contracts/generic_contract/src/contract.rs
+++ b/examples/contracts/generic_contract/src/contract.rs
@@ -1,6 +1,6 @@
 use cw_storage_plus::Item;
 use sylvia::contract;
-use sylvia::cw_std::{Reply, Response, StdResult};
+use sylvia::cw_std::{Binary, Response, StdResult, SubMsgResult};
 use sylvia::serde::Deserialize;
 use sylvia::types::{CustomMsg, ExecCtx, InstantiateCtx, MigrateCtx, QueryCtx, ReplyCtx, SudoCtx};
 
@@ -185,7 +185,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_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/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..1ba936b 100644
--- a/examples/contracts/generics_forwarded/src/contract.rs
+++ b/examples/contracts/generics_forwarded/src/contract.rs
@@ -1,7 +1,9 @@
+#![allow(clippy::type_complexity)]
+
 use crate::error::ContractError;
 use cw_storage_plus::Item;
 use sylvia::contract;
-use sylvia::cw_std::{Reply, Response};
+use sylvia::cw_std::{Binary, Response, SubMsgResult};
 use sylvia::serde::Deserialize;
 use sylvia::types::{
     CustomMsg, CustomQuery, ExecCtx, InstantiateCtx, MigrateCtx, QueryCtx, ReplyCtx, SudoCtx,
@@ -32,7 +34,6 @@ pub struct GenericsForwardedContract<
     FieldT,
 > {
     _field: Item<FieldT>,
-    #[allow(clippy::type_complexity)]
     _phantom: std::marker::PhantomData<(
         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/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/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/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-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-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/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/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/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/sylvia-derive/Cargo.toml b/sylvia-derive/Cargo.toml
index 4918d60..57ee7c7 100644
--- a/sylvia-derive/Cargo.toml
+++ b/sylvia-derive/Cargo.toml
@@ -14,6 +14,7 @@ readme = "../README.md"
 [features]
 mt = []
 cosmwasm_1_2 = []
+sv_replies = []
 
 [lib]
 proc-macro = true
@@ -42,6 +43,7 @@ sylvia = { path = "../sylvia", features = [
     "cosmwasm_1_2",
     "cosmwasm_1_3",
     "cosmwasm_1_4",
+    "sv_replies",
 ] }
 serde = { workspace = true }
 cosmwasm-schema = { workspace = true }
diff --git a/sylvia-derive/src/contract.rs b/sylvia-derive/src/contract.rs
index 36bfc6d..ae294c4 100644
--- a/sylvia-derive/src/contract.rs
+++ b/sylvia-derive/src/contract.rs
@@ -74,11 +74,12 @@ impl<'a> ContractInput<'a> {
             item,
             generics,
             custom,
+            error,
             ..
         } = self;
         let multitest_helpers = self.emit_multitest_helpers();
         let messages = self.emit_messages();
-        let contract_api = Api::new(item, generics, custom).emit();
+        let contract_api = Api::new(item, generics, custom, error).emit();
         let querier = self.emit_querier();
         let executor = self.emit_executor();
         let reply = self.emit_reply();
@@ -181,8 +182,12 @@ impl<'a> ContractInput<'a> {
     }
 
     fn emit_reply(&self) -> TokenStream {
-        let variants = MsgVariants::new(self.item.as_variants(), MsgType::Reply, &[], &None);
+        if cfg!(feature = "sv_replies") {
+            let variants = MsgVariants::new(self.item.as_variants(), MsgType::Reply, &[], &None);
 
-        Reply::new(&variants).emit()
+            Reply::new(self.item, &self.generics, &variants).emit()
+        } else {
+            quote! {}
+        }
     }
 }
diff --git a/sylvia-derive/src/contract/communication/api.rs b/sylvia-derive/src/contract/communication/api.rs
index 48d4907..22fb13d 100644
--- a/sylvia-derive/src/contract/communication/api.rs
+++ b/sylvia-derive/src/contract/communication/api.rs
@@ -1,6 +1,6 @@
 use crate::crate_module;
 use crate::parser::variant_descs::AsVariantDescs;
-use crate::parser::{Custom, MsgType};
+use crate::parser::{ContractErrorAttr, Custom, MsgType};
 use crate::types::msg_variant::MsgVariants;
 use crate::utils::emit_bracketed_generics;
 use proc_macro2::TokenStream;
@@ -16,10 +16,16 @@ pub struct Api<'a> {
     sudo_variants: MsgVariants<'a, GenericParam>,
     generics: &'a [&'a GenericParam],
     custom: &'a Custom,
+    error: &'a ContractErrorAttr,
 }
 
 impl<'a> Api<'a> {
-    pub fn new(source: &'a ItemImpl, generics: &'a [&'a GenericParam], custom: &'a Custom) -> Self {
+    pub fn new(
+        source: &'a ItemImpl,
+        generics: &'a [&'a GenericParam],
+        custom: &'a Custom,
+        error: &'a ContractErrorAttr,
+    ) -> Self {
         let exec_variants = MsgVariants::new(
             source.as_variants(),
             MsgType::Exec,
@@ -64,6 +70,7 @@ impl<'a> Api<'a> {
             sudo_variants,
             generics,
             custom,
+            error,
         }
     }
 
@@ -78,7 +85,7 @@ impl<'a> Api<'a> {
             sudo_variants,
             generics,
             custom,
-            ..
+            error,
         } = self;
 
         let where_clause = &source.generics.where_clause;
@@ -103,6 +110,7 @@ impl<'a> Api<'a> {
         };
         let custom_msg = custom.msg_or_default();
         let custom_query = custom.query_or_default();
+        let error = &error.error;
 
         quote! {
             impl #bracket_generics #sylvia ::types::ContractApi for #contract_name #where_clause {
@@ -118,6 +126,7 @@ impl<'a> Api<'a> {
                 type Querier<'querier> = #sylvia ::types::BoundQuerier<'querier, #custom_query, Self >;
                 type CustomMsg = #custom_msg;
                 type CustomQuery = #custom_query;
+                type Error = #error;
             }
         }
     }
diff --git a/sylvia-derive/src/contract/communication/reply.rs b/sylvia-derive/src/contract/communication/reply.rs
index 256a8b7..7c40350 100644
--- a/sylvia-derive/src/contract/communication/reply.rs
+++ b/sylvia-derive/src/contract/communication/reply.rs
@@ -1,60 +1,293 @@
 use convert_case::{Case, Casing};
-use itertools::Itertools;
 use proc_macro2::TokenStream;
+use proc_macro_error::emit_error;
 use quote::quote;
-use syn::{GenericParam, Ident};
+use syn::fold::Fold;
+use syn::{parse_quote, GenericParam, Ident, ItemImpl, Type};
 
-use crate::types::msg_variant::MsgVariants;
+use crate::crate_module;
+use crate::fold::StripGenerics;
+use crate::parser::attributes::msg::ReplyOn;
+use crate::parser::MsgType;
+use crate::types::msg_variant::{MsgVariant, MsgVariants};
 
 pub struct Reply<'a> {
-    variants: &'a MsgVariants<'a, GenericParam>,
+    source: &'a ItemImpl,
+    generics: &'a [&'a GenericParam],
+    reply_data: Vec<ReplyData<'a>>,
 }
 
 impl<'a> Reply<'a> {
-    pub fn new(variants: &'a MsgVariants<'a, GenericParam>) -> Self {
-        Self { variants }
+    pub fn new(
+        source: &'a ItemImpl,
+        generics: &'a [&'a GenericParam],
+        variants: &'a MsgVariants<'a, GenericParam>,
+    ) -> Self {
+        let reply_data = variants.as_reply_data();
+
+        Self {
+            source,
+            generics,
+            reply_data,
+        }
     }
 
     pub fn emit(&self) -> TokenStream {
-        let unique_handlers = self.emit_reply_ids();
+        let unique_handlers: Vec<_> = self.emit_reply_ids().collect();
+        let dispatch = self.emit_dispatch();
 
         quote! {
             #(#unique_handlers)*
+
+            #dispatch
         }
     }
 
-    fn emit_reply_ids(&self) -> impl Iterator<Item = TokenStream> + 'a {
-        self.variants
-            .as_reply_ids()
-            .enumerate()
-            .map(|(id, reply_id)| {
-                let id = id as u64;
+    /// 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,
+            ..
+        } = 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 error = parse_quote!( < #contract as #sylvia ::types::ContractApi>::Error);
+        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;
 
-                quote! {
-                    pub const #reply_id : u64 = #id ;
+                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 ;
+            }
+        })
     }
 }
 
 trait ReplyVariants<'a> {
-    fn as_reply_ids(&'a self) -> impl Iterator<Item = Ident> + '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_ids(&'a self) -> impl Iterator<Item = Ident> + 'a {
+    fn as_reply_data(&self) -> Vec<ReplyData> {
+        let mut reply_data: Vec<ReplyData> = vec![];
+
         self.variants()
-            .flat_map(|variant| {
-                if variant.msg_attr().handlers().is_empty() {
-                    return vec![variant.function_name()];
+            .flat_map(ReplyVariant::as_reply_data)
+            .for_each(|(reply_id, function_name, reply_on)| {
+                match reply_data
+                    .iter_mut()
+                    .find(|existing_data| existing_data.reply_id == reply_id)
+                {
+                    Some(existing_data)
+                        if existing_data
+                            .handlers
+                            .iter()
+                            .any(|(_, prev_reply_on)| prev_reply_on.excludes(&reply_on)) =>
+                    {
+                        existing_data.handlers.iter().for_each(
+                            |(prev_function_name, prev_reply_on)| {
+                                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_data.reply_id.to_string(), prev_reply_on, prev_function_name);
+                                )
+                            },
+                        )
+                    }
+                    Some(existing_data) => existing_data.handlers.push((function_name, reply_on)),
+                    None => reply_data.push(ReplyData::new(reply_id, function_name, reply_on)),
                 }
-                variant.msg_attr().handlers().iter().collect()
-            })
-            .unique()
+            });
+
+        reply_data
+    }
+}
+
+/// Maps single reply id with its handlers.
+struct ReplyData<'a> {
+    pub reply_id: Ident,
+    pub handlers: Vec<(&'a Ident, ReplyOn)>,
+}
+
+impl<'a> ReplyData<'a> {
+    pub fn new(reply_id: Ident, function_name: &'a Ident, reply_on: ReplyOn) -> Self {
+        Self {
+            reply_id,
+            handlers: vec![(function_name, reply_on)],
+        }
+    }
+
+    /// Emits success and failure match arms for a single `ReplyId`.
+    fn emit_match_arms(&self, contract: &Type, generics: &[&GenericParam]) -> TokenStream {
+        let Self { reply_id, handlers } = self;
+
+        let contract_turbofish: Type = if !generics.is_empty() {
+            let contract_name = StripGenerics.fold_type((contract.clone()).clone());
+            parse_quote! { #contract_name :: < #(#generics),* > }
+        } else {
+            parse_quote! { #contract }
+        };
+
+        let success_match_arm = emit_success_match_arm(handlers, &contract_turbofish);
+        let failure_match_arm = emit_failure_match_arm(handlers, &contract_turbofish);
+
+        quote! {
+            #reply_id => {
+                match result {
+                    #success_match_arm
+                    #failure_match_arm
+                }
+            }
+        }
+    }
+}
+
+/// 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(
+    handlers: &[(&Ident, ReplyOn)],
+    contract_turbofish: &Type,
+) -> TokenStream {
+    let sylvia = crate_module();
+
+    match handlers
+        .iter()
+        .find(|(_, reply_on)| reply_on == &ReplyOn::Success || reply_on == &ReplyOn::Always)
+    {
+        Some((function_name, reply_on)) if reply_on == &ReplyOn::Success => quote! {
+            #sylvia ::cw_std::SubMsgResult::Ok(sub_msg_resp) => {
+                #[allow(deprecated)]
+                let #sylvia ::cw_std::SubMsgResponse { events, data, msg_responses} = sub_msg_resp;
+                #contract_turbofish ::new(). #function_name ((deps, env, gas_used, events, msg_responses).into(), data, payload)
+            }
+        },
+        Some((function_name, reply_on)) if reply_on == &ReplyOn::Always => quote! {
+            #sylvia ::cw_std::SubMsgResult::Ok(_) => {
+                #contract_turbofish ::new(). #function_name ((deps, env, gas_used, vec![], vec![]).into(), result, payload)
+            }
+        },
+        _ => 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::Failure].
+/// In case neither [ReplyOn::Failure] nor [ReplyOn::Always] is present,
+/// the error is forwarded.
+fn emit_failure_match_arm(
+    handlers: &[(&Ident, ReplyOn)],
+    contract_turbofish: &Type,
+) -> TokenStream {
+    let sylvia = crate_module();
+
+    match handlers
+        .iter()
+        .find(|(_, reply_on)| reply_on == &ReplyOn::Failure || reply_on == &ReplyOn::Always)
+    {
+        Some((function_name, reply_on)) if reply_on == &ReplyOn::Failure => quote! {
+            #sylvia ::cw_std::SubMsgResult::Err(error) => {
+                #contract_turbofish ::new(). #function_name ((deps, env, gas_used, vec![], vec![]).into(), error, payload)
+            }
+        },
+        Some((function_name, reply_on)) if reply_on == &ReplyOn::Always => quote! {
+            #sylvia ::cw_std::SubMsgResult::Err(_) => {
+                #contract_turbofish ::new(). #function_name ((deps, env, gas_used, vec![], vec![]).into(), result, payload)
+            }
+        },
+        _ => quote! {
+            #sylvia ::cw_std::SubMsgResult::Err(error) => {
+                Err(sylvia::cw_std::StdError::generic_err(error)).map_err(Into::into)
+            }
+        },
+    }
+}
+
+trait ReplyVariant<'a> {
+    fn as_handlers(&'a self) -> Vec<&'a Ident>;
+    fn as_reply_data(&self) -> Vec<(Ident, &Ident, ReplyOn)>;
+}
+
+impl<'a> ReplyVariant<'a> for MsgVariant<'a> {
+    fn as_handlers(&'a self) -> Vec<&'a Ident> {
+        if self.msg_attr().handlers().is_empty() {
+            return vec![self.function_name()];
+        }
+        self.msg_attr().handlers().iter().collect()
+    }
+
+    fn as_reply_data(&self) -> Vec<(Ident, &Ident, ReplyOn)> {
+        self.as_handlers()
+            .iter()
             .map(|handler| {
-                let reply_id =
-                    format! {"{}_REPLY_ID", handler.to_string().to_case(Case::UpperSnake)};
-                Ident::new(&reply_id, handler.span())
+                (
+                    handler.as_reply_id(),
+                    self.function_name(),
+                    self.msg_attr().reply_on(),
+                )
             })
+            .collect()
+    }
+}
+
+/// 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/mt.rs b/sylvia-derive/src/contract/mt.rs
index 3a9ecb1..2da8aa1 100644
--- a/sylvia-derive/src/contract/mt.rs
+++ b/sylvia-derive/src/contract/mt.rs
@@ -531,10 +531,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 cfg!(feature = "sv_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(|| {
diff --git a/sylvia-derive/src/entry_points.rs b/sylvia-derive/src/entry_points.rs
index 33544c5..611c4af 100644
--- a/sylvia-derive/src/entry_points.rs
+++ b/sylvia-derive/src/entry_points.rs
@@ -177,7 +177,7 @@ impl<'a> EntryPoints<'a> {
         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 +201,15 @@ impl<'a> EntryPoints<'a> {
             _ => quote! { msg: < #contract as #sylvia ::types::ContractApi> :: #associated_name },
         };
         let dispatch = match msg_ty {
+            MsgType::Reply if cfg!(feature = "sv_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/lib.rs b/sylvia-derive/src/lib.rs
index f581f8a..3403daf 100644
--- a/sylvia-derive/src/lib.rs
+++ b/sylvia-derive/src/lib.rs
@@ -264,7 +264,7 @@ 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::cw_std::{Binary, Response, StdError, SubMsgResult};
 /// # use cw_storage_plus::Item;
 /// # use thiserror::Error;
 /// #
@@ -310,7 +310,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, payload: Binary) -> Result<Response, ContractError> {
 /// #        Ok(Response::new())
 ///     }
 ///
@@ -326,7 +326,7 @@ fn interface_impl(_attr: TokenStream2, item: TokenStream2) -> TokenStream2 {
 ///
 /// ```rust
 /// # use sylvia::types::{ExecCtx, InstantiateCtx, MigrateCtx, QueryCtx, ReplyCtx, SudoCtx};
-/// # use sylvia::cw_std::{Response, StdError};
+/// # use sylvia::cw_std::{Binary, Response, StdError, SubMsgResult};
 /// # use cw_storage_plus::Item;
 /// # use thiserror::Error;
 /// #
@@ -655,7 +655,7 @@ 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::cw_std::{Binary, Reply, Response, StdResult, SubMsgResult};
 /// #
 /// pub struct SvContract;
 ///
@@ -687,7 +687,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, payload: Binary) -> StdResult<Response> {
 /// #        Ok(Response::new())
 /// #    }
 /// #
diff --git a/sylvia-derive/src/parser/attributes/msg.rs b/sylvia-derive/src/parser/attributes/msg.rs
index 16e1f03..85b1e2f 100644
--- a/sylvia-derive/src/parser/attributes/msg.rs
+++ b/sylvia-derive/src/parser/attributes/msg.rs
@@ -68,7 +68,7 @@ impl Parse for ArgumentParser {
 }
 
 /// Representation of `reply_on` parameter in `#[sv::msg(reply(...))]` attribute.
-#[derive(Debug, Default, Clone)]
+#[derive(Copy, Debug, Default, Clone, PartialEq)]
 pub enum ReplyOn {
     Success,
     Failure,
@@ -76,6 +76,16 @@ pub enum ReplyOn {
     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::Failure => f.write_str("failure"),
+            ReplyOn::Always => f.write_str("always"),
+        }
+    }
+}
+
 impl ReplyOn {
     pub fn new(reply_on: Ident) -> Result<Self> {
         match reply_on.to_string().as_str() {
@@ -88,6 +98,15 @@ impl ReplyOn {
             )),
         }
     }
+
+    /// 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.
@@ -96,7 +115,7 @@ pub struct MsgAttr {
     msg_type: MsgType,
     query_resp_type: Option<Ident>,
     reply_handlers: Vec<Ident>,
-    _reply_on: ReplyOn,
+    reply_on: ReplyOn,
 }
 
 impl MsgAttr {
@@ -118,6 +137,10 @@ impl MsgAttr {
     pub fn handlers(&self) -> &[Ident] {
         &self.reply_handlers
     }
+
+    pub fn reply_on(&self) -> ReplyOn {
+        self.reply_on
+    }
 }
 
 impl PartialEq<MsgType> for MsgAttr {
@@ -140,7 +163,7 @@ impl Parse for MsgAttr {
             msg_type,
             query_resp_type,
             reply_handlers,
-            _reply_on: reply_on.unwrap_or_default(),
+            reply_on: reply_on.unwrap_or_default(),
         })
     }
 }
diff --git a/sylvia/Cargo.toml b/sylvia/Cargo.toml
index 92df9d7..fa4cf17 100644
--- a/sylvia/Cargo.toml
+++ b/sylvia/Cargo.toml
@@ -38,6 +38,8 @@ cosmwasm_2_0 = [
     "cw-multi-test/cosmwasm_2_0",
     "cosmwasm_1_4",
 ]
+# Enables better repli
+sv_replies = ["sylvia-derive/sv_replies"]
 
 [dependencies]
 sylvia-derive = { workspace = true }
diff --git a/sylvia/src/types.rs b/sylvia/src/types.rs
index 0c0e937..cbf12d3 100644
--- a/sylvia/src/types.rs
+++ b/sylvia/src/types.rs
@@ -1,5 +1,7 @@
 //! Module providing utilities to build and use sylvia contracts.
 use cosmwasm_std::{Binary, Coin, Deps, DepsMut, Empty, Env, MessageInfo, WasmMsg};
+#[cfg(feature = "sv_replies")]
+use cosmwasm_std::{Event, MsgResponse};
 use derivative::Derivative;
 use schemars::JsonSchema;
 use serde::de::DeserializeOwned;
@@ -441,45 +443,50 @@ 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.
+#[cfg(feature = "sv_replies")]
 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 `reply` context received in entry point as
-/// (DepsMut, Env) tuple.
+/// Represantation of `reply` context received in entry point.
+#[cfg(not(feature = "sv_replies"))]
+pub struct ReplyCtx<'a, C: cosmwasm_std::CustomQuery = Empty> {
+    pub deps: DepsMut<'a, C>,
+    pub env: Env,
+}
+
+/// Represantation of `migrate` context received in entry point.
 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.
 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.
 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.
 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.
 pub struct SudoCtx<'a, C: cosmwasm_std::CustomQuery = Empty> {
     pub deps: DepsMut<'a, C>,
     pub env: Env,
@@ -520,6 +527,30 @@ impl<'a, C: cosmwasm_std::CustomQuery> From<(DepsMut<'a, C>, Env)> for MigrateCt
     }
 }
 
+#[cfg(feature = "sv_replies")]
+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,
+        }
+    }
+}
+
+#[cfg(not(feature = "sv_replies"))]
 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 }
@@ -588,4 +619,5 @@ pub trait ContractApi {
     type Remote<'remote>;
     type CustomMsg: CustomMsg;
     type CustomQuery: CustomQuery;
+    type Error: From<cosmwasm_std::StdError>;
 }


Test patch:
diff --git a/sylvia/src/multitest.rs b/sylvia/src/multitest.rs
index 39ad4f8..541fdbd 100644
--- a/sylvia/src/multitest.rs
+++ b/sylvia/src/multitest.rs
@@ -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/replies.rs b/sylvia/tests/legacy_replies.rs
similarity index 92%
rename from sylvia/tests/replies.rs
rename to sylvia/tests/legacy_replies.rs
index 663b255..16c05b3 100644
--- a/sylvia/tests/replies.rs
+++ b/sylvia/tests/legacy_replies.rs
@@ -1,3 +1,5 @@
+#![cfg(not(feature = "sv_replies"))]
+
 #[cfg(all(test, feature = "mt"))]
 use cw_multi_test::IntoBech32;
 use sylvia::cw_std::testing::{mock_dependencies, mock_env};
@@ -32,10 +34,11 @@ mod noop_contract {
 }
 
 mod reply_contract {
+    use cosmwasm_std::{Binary, Reply, SubMsgResult};
     use sylvia::types::{ExecCtx, InstantiateCtx, 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,7 @@ mod reply_contract {
         }
 
         #[sv::msg(reply)]
-        fn reply(&self, _ctx: ReplyCtx, _msg: Reply) -> StdResult<Response> {
+        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..439feba 100644
--- a/sylvia/tests/messages_generation.rs
+++ b/sylvia/tests/messages_generation.rs
@@ -1,3 +1,4 @@
+#![cfg(not(feature = "sv_replies"))]
 use std::fmt::Debug;
 use std::str::FromStr;
 
@@ -65,6 +66,7 @@ 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};
diff --git a/sylvia/tests/reply.rs b/sylvia/tests/reply.rs
index e0b5763..25d7778 100644
--- a/sylvia/tests/reply.rs
+++ b/sylvia/tests/reply.rs
@@ -1,74 +1,274 @@
-use sylvia::contract;
-use sylvia::cw_std::{Reply, Response, StdResult};
-use sylvia::types::{InstantiateCtx, ReplyCtx};
+#![cfg(feature = "sv_replies")]
 
-pub struct Contract;
+use cosmwasm_std::SubMsgResult;
+use cw_storage_plus::Item;
+use cw_utils::{parse_instantiate_response_data, ParseReplyError};
+use noop_contract::sv::Executor;
+use sv::{ALWAYS_REPLY_ID, FAILURE_REPLY_ID, REMOTE_INSTANTIATED_REPLY_ID, SUCCESS_REPLY_ID};
+use sylvia::cw_std::{to_json_binary, Addr, Binary, ReplyOn, Response, StdError, SubMsg, WasmMsg};
+use sylvia::types::{ExecCtx, InstantiateCtx, QueryCtx, Remote, ReplyCtx};
+use sylvia::{contract, entry_points};
+use thiserror::Error;
 
+#[allow(dead_code)]
+mod noop_contract {
+    use cosmwasm_std::{StdError, StdResult};
+    use sylvia::types::{ExecCtx, InstantiateCtx};
+    use sylvia::{contract, entry_points};
+
+    use sylvia::cw_std::Response;
+
+    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, should_fail: bool) -> StdResult<Response> {
+            if should_fail {
+                Err(StdError::generic_err("Failed as requested"))
+            } else {
+                Ok(Response::new())
+            }
+        }
+    }
+}
+
+#[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>>,
+    last_reply: Item<u64>,
+}
+
+#[entry_points]
 #[contract]
+#[sv::error(ContractError)]
 impl Contract {
     pub fn new() -> Self {
-        Self
+        Self {
+            remote: Item::new("remote"),
+            last_reply: Item::new("last_reply"),
+        }
     }
 
     #[sv::msg(instantiate)]
-    pub fn instantiate(&self, _ctx: InstantiateCtx) -> StdResult<Response> {
-        Ok(Response::new())
+    pub fn instantiate(
+        &self,
+        _ctx: InstantiateCtx,
+        remote_code_id: u64,
+    ) -> Result<Response, ContractError> {
+        let msg = WasmMsg::Instantiate {
+            admin: None,
+            code_id: remote_code_id,
+            msg: to_json_binary(&noop_contract::sv::InstantiateMsg::new())?,
+            funds: vec![],
+            label: "noop".to_string(),
+        };
+        let sub_msg = SubMsg::reply_on_success(msg, REMOTE_INSTANTIATED_REPLY_ID);
+        Ok(Response::new().add_submessage(sub_msg))
     }
 
-    #[sv::msg(reply)]
-    fn clean(&self, _ctx: ReplyCtx, _reply: Reply) -> StdResult<Response> {
-        Ok(Response::new())
+    #[sv::msg(exec)]
+    pub fn call_remote(
+        &self,
+        ctx: ExecCtx,
+        reply_id: u64,
+        should_fail: bool,
+        reply_on: ReplyOn,
+    ) -> Result<Response, ContractError> {
+        let msg = self
+            .remote
+            .load(ctx.deps.storage)?
+            .executor()
+            .noop(should_fail)?
+            .build();
+        let mut msg = SubMsg::new(msg);
+        msg.id = reply_id;
+        msg.reply_on = reply_on;
+
+        Ok(Response::new().add_submessage(msg))
+    }
+
+    #[sv::msg(query)]
+    pub fn last_reply(&self, ctx: QueryCtx) -> Result<u64, ContractError> {
+        self.last_reply.load(ctx.deps.storage).map_err(Into::into)
     }
 
-    #[allow(dead_code)]
-    #[sv::msg(reply, handlers=[handler_one, handler_two])]
-    fn custom_handlers(&self, _ctx: ReplyCtx, _reply: Reply) -> StdResult<Response> {
+    #[sv::msg(reply, reply_on=success)]
+    fn remote_instantiated(
+        &self,
+        ctx: ReplyCtx,
+        data: Option<Binary>,
+        _payload: Binary,
+    ) -> Result<Response, 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);
+
+        self.remote
+            .save(ctx.deps.storage, &Remote::new(remote_addr))?;
+
         Ok(Response::new())
     }
 
-    #[allow(dead_code)]
-    #[sv::msg(reply, reply_on = success)]
-    fn reply_on(&self, _ctx: ReplyCtx, _reply: Reply) -> StdResult<Response> {
+    #[sv::msg(reply, handlers=[success, both], reply_on=success)]
+    fn success(
+        &self,
+        ctx: ReplyCtx,
+        _data: Option<Binary>,
+        _payload: Binary,
+    ) -> Result<Response, ContractError> {
+        self.last_reply.save(ctx.deps.storage, &SUCCESS_REPLY_ID)?;
+
         Ok(Response::new())
     }
 
-    #[allow(dead_code)]
-    #[sv::msg(reply, reply_on = always)]
-    fn reply_on_always(&self, _ctx: ReplyCtx, _reply: Reply) -> StdResult<Response> {
+    #[sv::msg(reply, handlers=[failure, both], reply_on=failure)]
+    fn failure(
+        &self,
+        ctx: ReplyCtx,
+        _error: String,
+        _payload: Binary,
+    ) -> Result<Response, ContractError> {
+        self.last_reply.save(ctx.deps.storage, &FAILURE_REPLY_ID)?;
+
         Ok(Response::new())
     }
 
-    #[allow(dead_code)]
-    #[sv::msg(reply, handlers=[handler_one, handler_two], reply_on = failure)]
-    fn both_parameters(&self, _ctx: ReplyCtx, _reply: Reply) -> StdResult<Response> {
+    #[sv::msg(reply, reply_on=always)]
+    fn always(
+        &self,
+        ctx: ReplyCtx,
+        _result: SubMsgResult,
+        _payload: Binary,
+    ) -> Result<Response, ContractError> {
+        self.last_reply.save(ctx.deps.storage, &ALWAYS_REPLY_ID)?;
+
         Ok(Response::new())
     }
 }
 
-#[cfg(test)]
 mod tests {
-    use itertools::Itertools;
+    use crate::noop_contract::sv::mt::CodeId as NoopCodeId;
+    use crate::sv::mt::{CodeId, ContractProxy};
+    use crate::sv::{
+        ALWAYS_REPLY_ID, BOTH_REPLY_ID, FAILURE_REPLY_ID, REMOTE_INSTANTIATED_REPLY_ID,
+        SUCCESS_REPLY_ID,
+    };
+
+    use sylvia::cw_multi_test::IntoBech32;
+    use sylvia::cw_std::{ReplyOn, StdError};
+    use sylvia::multitest::App;
 
     #[test]
-    fn reply_id_generation() {
-        // Assert IDs uniqueness
-        let unique_ids_count = [
-            super::sv::CLEAN_REPLY_ID,
-            super::sv::HANDLER_ONE_REPLY_ID,
-            super::sv::HANDLER_TWO_REPLY_ID,
-            super::sv::REPLY_ON_REPLY_ID,
-            super::sv::REPLY_ON_ALWAYS_REPLY_ID,
-        ]
-        .iter()
-        .unique()
-        .count();
-
-        assert_eq!(unique_ids_count, 5);
-
-        assert_eq!(super::sv::CLEAN_REPLY_ID, 0);
-        assert_eq!(super::sv::HANDLER_ONE_REPLY_ID, 1);
-        assert_eq!(super::sv::HANDLER_TWO_REPLY_ID, 2);
-        assert_eq!(super::sv::REPLY_ON_REPLY_ID, 3);
-        assert_eq!(super::sv::REPLY_ON_ALWAYS_REPLY_ID, 4);
+    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_REPLY_ID, true, ReplyOn::Success)
+            .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_REPLY_ID, false, ReplyOn::Success)
+            .call(&owner)
+            .unwrap();
+        let last_reply = contract.last_reply().unwrap();
+        assert_eq!(last_reply, SUCCESS_REPLY_ID);
+
+        // Should not dispatch if expected failure and execution succeeded
+        contract
+            .call_remote(FAILURE_REPLY_ID, false, ReplyOn::Error)
+            .call(&owner)
+            .unwrap();
+        let last_reply = contract.last_reply().unwrap();
+        assert_eq!(last_reply, SUCCESS_REPLY_ID);
+
+        // Should dispatch if expected failure and execution failed
+        contract
+            .call_remote(FAILURE_REPLY_ID, true, ReplyOn::Error)
+            .call(&owner)
+            .unwrap();
+        let last_reply = contract.last_reply().unwrap();
+        assert_eq!(last_reply, FAILURE_REPLY_ID);
+
+        // Should dispatch if expected any result and execution succeeded
+        contract
+            .call_remote(ALWAYS_REPLY_ID, false, ReplyOn::Always)
+            .call(&owner)
+            .unwrap();
+        let last_reply = contract.last_reply().unwrap();
+        assert_eq!(last_reply, ALWAYS_REPLY_ID);
+
+        // Should dispatch if expected any result and execution failed
+        contract
+            .call_remote(ALWAYS_REPLY_ID, true, ReplyOn::Always)
+            .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_REPLY_ID, false, ReplyOn::Success)
+            .call(&owner)
+            .unwrap();
+        let last_reply = contract.last_reply().unwrap();
+        assert_eq!(last_reply, SUCCESS_REPLY_ID);
+
+        // Should dispatch if expected both results and execution failed
+        contract
+            .call_remote(BOTH_REPLY_ID, true, ReplyOn::Error)
+            .call(&owner)
+            .unwrap();
+        let last_reply = contract.last_reply().unwrap();
+        assert_eq!(last_reply, FAILURE_REPLY_ID);
+
+        // Should return error if unknown reply ID received
+        let unknown_reply_id = 42u64;
+        let err = contract
+            .call_remote(unknown_reply_id, false, ReplyOn::Success)
+            .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..171fa6b
--- /dev/null
+++ b/sylvia/tests/reply_generation.rs
@@ -0,0 +1,99 @@
+#![cfg(feature = "sv_replies")]
+
+use cosmwasm_std::{Binary, SubMsgResult};
+use itertools::Itertools;
+use sylvia::cw_std::{Response, StdResult};
+use sylvia::types::{InstantiateCtx, ReplyCtx};
+use sylvia::{contract, entry_points};
+
+pub struct Contract;
+
+#[entry_points]
+#[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)]
+    fn clean(
+        &self,
+        _ctx: ReplyCtx,
+        _result: SubMsgResult,
+        _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,
+        _payload: Binary,
+    ) -> StdResult<Response> {
+        Ok(Response::new())
+    }
+
+    #[allow(dead_code)]
+    #[sv::msg(reply, reply_on = success)]
+    fn reply_on(
+        &self,
+        _ctx: ReplyCtx,
+        _data: Option<Binary>,
+        _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,
+        _payload: Binary,
+    ) -> StdResult<Response> {
+        Ok(Response::new())
+    }
+
+    #[allow(dead_code)]
+    #[sv::msg(reply, handlers=[reply_on], reply_on = failure)]
+    fn both_parameters(
+        &self,
+        _ctx: ReplyCtx,
+        _error: String,
+        _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::REPLY_ON_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::REPLY_ON_REPLY_ID, 3);
+    assert_eq!(sv::REPLY_ON_ALWAYS_REPLY_ID, 4);
+}
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..0a18cd8
--- /dev/null
+++ b/sylvia/tests/ui/attributes/msg/overlapping_reply_handlers.rs
@@ -0,0 +1,46 @@
+#![allow(unused_imports)]
+#![cfg(feature = "sv_replies")]
+
+use sylvia::contract;
+use sylvia::cw_std::{Reply, Response, StdResult};
+use sylvia::types::{InstantiateCtx, ReplyCtx};
+
+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, handlers=[handler1])]
+    fn reply_always(&self, _ctx: ReplyCtx, _reply: Reply) -> StdResult<Response> {
+        Ok(Response::new())
+    }
+
+    #[sv::msg(reply, handlers=[handler1], reply_on=success)]
+    fn duplicated_success_for_reply_always(
+        &self,
+        _ctx: ReplyCtx,
+        _reply: Reply,
+    ) -> StdResult<Response> {
+        Ok(Response::new())
+    }
+
+    #[sv::msg(reply, handlers=[handler2], reply_on=failure)]
+    fn some_reply(&self, _ctx: ReplyCtx, _reply: Reply) -> StdResult<Response> {
+        Ok(Response::new())
+    }
+
+    #[sv::msg(reply, reply_on=failure)]
+    fn handler2(&self, _ctx: ReplyCtx, _reply: Reply) -> 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..80c211b
--- /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:26:32
+   |
+26 |     #[sv::msg(reply, handlers=[handler1], reply_on=success)]
+   |                                ^^^^^^^^
+
+error: Duplicated reply handler.
+
+         = note: Previous definition of handler=HANDLER_2_REPLY_ID for reply_on=failure defined on `fn some_reply()`
+
+  --> tests/ui/attributes/msg/overlapping_reply_handlers.rs:41:8
+   |
+41 |     fn handler2(&self, _ctx: ReplyCtx, _reply: Reply) -> StdResult<Response> {
+   |        ^^^^^^^^