Repository: CosmWasm/cosmwasm
Pull request #2403 (2025-03-06 17:19:44)

feat: IBCv2 receive entrypoint


Patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index c933dc7d8..9a3f32c89 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -78,6 +78,7 @@ workflows:
       - contract_crypto_verify
       - contract_cyberpunk
       - contract_empty
+      - contract_ibc2
       - contract_replier
       # - contract_floaty # This contract needs nightly Rust to compile
       - contract_hackatom
@@ -167,6 +168,10 @@ jobs:
           name: "contracts/hackatom: integration-test"
           working_directory: ~/project/contracts/hackatom
           command: cargo wasm --locked && cargo integration-test --locked
+      - run:
+          name: "contracts/ibc2: integration-test"
+          working_directory: ~/project/contracts/ibc2
+          command: cargo wasm --locked
       - run:
           name: "contracts/queue: integration-test"
           working_directory: ~/project/contracts/queue
@@ -195,7 +200,7 @@ jobs:
           name: "packages/vm: test"
           working_directory: ~/project/packages/vm
           # use all features
-          command: cargo test --locked --features iterator,staking,stargate
+          command: cargo test --locked --features iterator,staking,stargate,ibc2
       - save_cache:
           key: v4-arm64-workspace-rust:1.81-{{ checksum "Cargo.lock" }}
           paths:
@@ -221,6 +226,13 @@ jobs:
             - contracts/hackatom/target/wasm32-unknown-unknown/release/.fingerprint
             - contracts/hackatom/target/wasm32-unknown-unknown/release/build
             - contracts/hackatom/target/wasm32-unknown-unknown/release/deps
+            # ibc2
+            - contracts/ibc2/target/debug/.fingerprint
+            - contracts/ibc2/target/debug/build
+            - contracts/ibc2/target/debug/deps
+            - contracts/ibc2/target/wasm32-unknown-unknown/release/.fingerprint
+            - contracts/ibc2/target/wasm32-unknown-unknown/release/build
+            - contracts/ibc2/target/wasm32-unknown-unknown/release/deps
             # queue
             - contracts/queue/target/debug/.fingerprint
             - contracts/queue/target/debug/build
@@ -688,6 +700,35 @@ jobs:
             - target/wasm32-unknown-unknown/release/deps
           key: cargocache-v2-contract_cyberpunk-rust:1.81-{{ checksum "Cargo.lock" }}
 
+  contract_ibc2:
+    docker:
+      - image: rust:1.81
+    environment:
+      RUST_BACKTRACE: 1
+    working_directory: ~/cosmwasm/contracts/ibc2
+    steps:
+      - checkout:
+          path: ~/cosmwasm
+      - run:
+          name: Version information
+          command: rustc --version; cargo --version; rustup --version
+      - restore_cache:
+          keys:
+            - cargocache-v2-contract_ibc2-rust:1.74-{{ checksum "Cargo.lock" }}
+      - check_contract:
+          skip_cosmwasm_check: true
+          min_version: "2.2"
+      - save_cache:
+          paths:
+            - /usr/local/cargo/registry
+            - target/debug/.fingerprint
+            - target/debug/build
+            - target/debug/deps
+            - target/wasm32-unknown-unknown/release/.fingerprint
+            - target/wasm32-unknown-unknown/release/build
+            - target/wasm32-unknown-unknown/release/deps
+          key: cargocache-v2-contract_ibc2-rust:1.74-{{ checksum "Cargo.lock" }}
+
   contract_hackatom:
     docker:
       - image: rust:1.81
diff --git a/.mergify.yml b/.mergify.yml
index 4895cfe74..c665df4f8 100644
--- a/.mergify.yml
+++ b/.mergify.yml
@@ -18,6 +18,7 @@ pull_request_rules:
       - "status-success=ci/circleci: contract_burner"
       - "status-success=ci/circleci: contract_crypto_verify"
       - "status-success=ci/circleci: contract_cyberpunk"
+      - "status-success=ci/circleci: contract_ibc2"
       - "status-success=ci/circleci: contract_floaty"
       - "status-success=ci/circleci: contract_hackatom"
       - "status-success=ci/circleci: contract_hackatom"
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 350af2016..e81671cf0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,10 +15,12 @@ and this project adheres to
   `From<Int128> for i128` ([#2268])
 - cosmwasm-std: Implement `Uint128::from_{be,le}_bytes` and
   `Uint64::from_{be,le}_bytes`. ([#2269])
-- cosmwasm-std: Added new `EurekaMsg` and `CosmosMsg::Eureka` variant ([#2340])
+- cosmwasm-std: Added new `Ibc2Msg` and `CosmosMsg::Ibc2` variant ([#2390],
+  [#2403])
 - cosmwasm-std: Implement downcasting for `Api` trait. This allows using
   `MockApi::addr_make` from `DepsMut`. ([#2383])
-- cosmwasm-std: Added `eureka_port` to `ContractInfoResponse`. ([#2390])
+- cosmwasm-std: Added `ibc2_port` to `ContractInfoResponse`. ([#2390], [#2403])
+- cosmwasm-vm: Added `ibc2_packet_receive` entrypoint ([#2403])
 
 ## Changed
 
@@ -74,6 +76,7 @@ and this project adheres to
 [#2383]: https://github.com/CosmWasm/cosmwasm/issues/2383
 [#2390]: https://github.com/CosmWasm/cosmwasm/issues/2390
 [#2399]: https://github.com/CosmWasm/cosmwasm/pull/2399
+[#2403]: https://github.com/CosmWasm/cosmwasm/pull/2403
 
 ## [2.2.0] - 2024-12-17
 
diff --git a/contracts/README.md b/contracts/README.md
index 05f3c0445..74e6a7c1c 100644
--- a/contracts/README.md
+++ b/contracts/README.md
@@ -113,6 +113,11 @@ docker run --rm -v "$(pwd)":/code \
   --mount type=volume,source="devcontract_cache_empty",target=/target \
   --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \
   cosmwasm/optimizer:0.15.0 ./contracts/empty
+
+docker run --rm -v "$(pwd)":/code \
+  --mount type=volume,source="devcontract_cache_ibc2",target=/target \
+  --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \
+  cosmwasm/optimizer:0.15.0 ./contracts/ibc2
 ```
 
 ## Entry points
diff --git a/contracts/ibc2/.cargo/config.toml b/contracts/ibc2/.cargo/config.toml
new file mode 100644
index 000000000..f5174787c
--- /dev/null
+++ b/contracts/ibc2/.cargo/config.toml
@@ -0,0 +1,6 @@
+[alias]
+wasm = "build --release --lib --target wasm32-unknown-unknown"
+wasm-debug = "build --lib --target wasm32-unknown-unknown"
+unit-test = "test --lib"
+integration-test = "test --test integration"
+schema = "run --bin schema"
diff --git a/contracts/ibc2/Cargo.lock b/contracts/ibc2/Cargo.lock
new file mode 100644
index 000000000..d8bf00be6
--- /dev/null
+++ b/contracts/ibc2/Cargo.lock
@@ -0,0 +1,1045 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "ahash"
+version = "0.8.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "version_check",
+ "zerocopy",
+]
+
+[[package]]
+name = "allocator-api2"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
+
+[[package]]
+name = "ark-bls12-381"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c775f0d12169cba7aae4caeb547bb6a50781c7449a8aa53793827c9ec4abf488"
+dependencies = [
+ "ark-ec",
+ "ark-ff",
+ "ark-serialize",
+ "ark-std",
+]
+
+[[package]]
+name = "ark-ec"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba"
+dependencies = [
+ "ark-ff",
+ "ark-poly",
+ "ark-serialize",
+ "ark-std",
+ "derivative",
+ "hashbrown 0.13.2",
+ "itertools",
+ "num-traits",
+ "rayon",
+ "zeroize",
+]
+
+[[package]]
+name = "ark-ff"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba"
+dependencies = [
+ "ark-ff-asm",
+ "ark-ff-macros",
+ "ark-serialize",
+ "ark-std",
+ "derivative",
+ "digest",
+ "itertools",
+ "num-bigint",
+ "num-traits",
+ "paste",
+ "rayon",
+ "rustc_version",
+ "zeroize",
+]
+
+[[package]]
+name = "ark-ff-asm"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348"
+dependencies = [
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-ff-macros"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565"
+dependencies = [
+ "num-bigint",
+ "num-traits",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-poly"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf"
+dependencies = [
+ "ark-ff",
+ "ark-serialize",
+ "ark-std",
+ "derivative",
+ "hashbrown 0.13.2",
+]
+
+[[package]]
+name = "ark-serialize"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5"
+dependencies = [
+ "ark-serialize-derive",
+ "ark-std",
+ "digest",
+ "num-bigint",
+]
+
+[[package]]
+name = "ark-serialize-derive"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-std"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185"
+dependencies = [
+ "num-traits",
+ "rand",
+ "rayon",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
+
+[[package]]
+name = "base16ct"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
+
+[[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
+[[package]]
+name = "bech32"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d"
+
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "bnum"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e31ea183f6ee62ac8b8a8cf7feddd766317adfb13ff469de57ce033efd6a790"
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "const-oid"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
+
+[[package]]
+name = "cosmwasm-core"
+version = "2.2.0-rc.1"
+
+[[package]]
+name = "cosmwasm-crypto"
+version = "2.2.0-rc.1"
+dependencies = [
+ "ark-bls12-381",
+ "ark-ec",
+ "ark-ff",
+ "ark-serialize",
+ "cosmwasm-core",
+ "curve25519-dalek",
+ "digest",
+ "ecdsa",
+ "ed25519-zebra",
+ "k256",
+ "num-traits",
+ "p256",
+ "rand_core",
+ "rayon",
+ "sha2",
+ "thiserror",
+]
+
+[[package]]
+name = "cosmwasm-derive"
+version = "2.2.0-rc.1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.98",
+]
+
+[[package]]
+name = "cosmwasm-schema"
+version = "2.2.0-rc.1"
+dependencies = [
+ "cosmwasm-schema-derive",
+ "schemars",
+ "serde",
+ "serde_json",
+ "thiserror",
+]
+
+[[package]]
+name = "cosmwasm-schema-derive"
+version = "2.2.0-rc.1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.98",
+]
+
+[[package]]
+name = "cosmwasm-std"
+version = "2.2.0-rc.1"
+dependencies = [
+ "base64",
+ "bech32",
+ "bnum",
+ "cosmwasm-core",
+ "cosmwasm-crypto",
+ "cosmwasm-derive",
+ "derive_more",
+ "downcast-rs",
+ "hex",
+ "rand_core",
+ "rmp-serde",
+ "schemars",
+ "serde",
+ "serde-json-wasm",
+ "sha2",
+ "static_assertions",
+ "thiserror",
+]
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
+dependencies = [
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
+
+[[package]]
+name = "crypto-bigint"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76"
+dependencies = [
+ "generic-array",
+ "rand_core",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "crypto-common"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "curve25519-dalek"
+version = "4.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "curve25519-dalek-derive",
+ "digest",
+ "fiat-crypto",
+ "rustc_version",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "curve25519-dalek-derive"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.98",
+]
+
+[[package]]
+name = "der"
+version = "0.7.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0"
+dependencies = [
+ "const-oid",
+ "zeroize",
+]
+
+[[package]]
+name = "derivative"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "derive_more"
+version = "1.0.0-beta.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7abbfc297053be59290e3152f8cbcd52c8642e0728b69ee187d991d4c1af08d"
+dependencies = [
+ "derive_more-impl",
+]
+
+[[package]]
+name = "derive_more-impl"
+version = "1.0.0-beta.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2bba3e9872d7c58ce7ef0fcf1844fcc3e23ef2a58377b50df35dd98e42a5726e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.98",
+ "unicode-xid",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "const-oid",
+ "crypto-common",
+ "subtle",
+]
+
+[[package]]
+name = "downcast-rs"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea8a8b81cacc08888170eef4d13b775126db426d0b348bee9d18c2c1eaf123cf"
+
+[[package]]
+name = "dyn-clone"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "feeef44e73baff3a26d371801df019877a9866a8c493d315ab00177843314f35"
+
+[[package]]
+name = "ecdsa"
+version = "0.16.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca"
+dependencies = [
+ "der",
+ "digest",
+ "elliptic-curve",
+ "rfc6979",
+ "signature",
+]
+
+[[package]]
+name = "ed25519"
+version = "2.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
+dependencies = [
+ "signature",
+]
+
+[[package]]
+name = "ed25519-zebra"
+version = "4.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d9ce6874da5d4415896cd45ffbc4d1cfc0c4f9c079427bd870742c30f2f65a9"
+dependencies = [
+ "curve25519-dalek",
+ "ed25519",
+ "hashbrown 0.14.5",
+ "hex",
+ "rand_core",
+ "sha2",
+ "zeroize",
+]
+
+[[package]]
+name = "either"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
+
+[[package]]
+name = "elliptic-curve"
+version = "0.13.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"
+dependencies = [
+ "base16ct",
+ "crypto-bigint",
+ "digest",
+ "ff",
+ "generic-array",
+ "group",
+ "rand_core",
+ "sec1",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "ff"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449"
+dependencies = [
+ "rand_core",
+ "subtle",
+]
+
+[[package]]
+name = "fiat-crypto"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
+
+[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+ "zeroize",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "group"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63"
+dependencies = [
+ "ff",
+ "rand_core",
+ "subtle",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
+dependencies = [
+ "ahash",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.14.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
+dependencies = [
+ "ahash",
+ "allocator-api2",
+]
+
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
+[[package]]
+name = "hmac"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
+dependencies = [
+ "digest",
+]
+
+[[package]]
+name = "ibc2"
+version = "0.0.0"
+dependencies = [
+ "cosmwasm-schema",
+ "cosmwasm-std",
+ "schemars",
+ "serde",
+ "thiserror",
+]
+
+[[package]]
+name = "itertools"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
+
+[[package]]
+name = "k256"
+version = "0.13.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b"
+dependencies = [
+ "cfg-if",
+ "ecdsa",
+ "elliptic-curve",
+ "sha2",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.169"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
+
+[[package]]
+name = "memchr"
+version = "2.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
+
+[[package]]
+name = "num-bigint"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
+dependencies = [
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.20.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
+
+[[package]]
+name = "p256"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b"
+dependencies = [
+ "ecdsa",
+ "elliptic-curve",
+ "primeorder",
+ "sha2",
+]
+
+[[package]]
+name = "paste"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
+dependencies = [
+ "zerocopy",
+]
+
+[[package]]
+name = "primeorder"
+version = "0.13.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6"
+dependencies = [
+ "elliptic-curve",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.93"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.38"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "rayon"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
+dependencies = [
+ "either",
+ "rayon-core",
+]
+
+[[package]]
+name = "rayon-core"
+version = "1.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
+dependencies = [
+ "crossbeam-deque",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "rfc6979"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2"
+dependencies = [
+ "hmac",
+ "subtle",
+]
+
+[[package]]
+name = "rmp"
+version = "0.8.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4"
+dependencies = [
+ "byteorder",
+ "num-traits",
+ "paste",
+]
+
+[[package]]
+name = "rmp-serde"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52e599a477cf9840e92f2cde9a7189e67b42c57532749bf90aea6ec10facd4db"
+dependencies = [
+ "byteorder",
+ "rmp",
+ "serde",
+]
+
+[[package]]
+name = "rustc_version"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd"
+
+[[package]]
+name = "schemars"
+version = "0.8.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92"
+dependencies = [
+ "dyn-clone",
+ "schemars_derive",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "schemars_derive"
+version = "0.8.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "serde_derive_internals",
+ "syn 2.0.98",
+]
+
+[[package]]
+name = "sec1"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc"
+dependencies = [
+ "base16ct",
+ "der",
+ "generic-array",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "semver"
+version = "1.0.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03"
+
+[[package]]
+name = "serde"
+version = "1.0.217"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde-json-wasm"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f05da0d153dd4595bdffd5099dc0e9ce425b205ee648eb93437ff7302af8c9a5"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.217"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.98",
+]
+
+[[package]]
+name = "serde_derive_internals"
+version = "0.29.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.98",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.138"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949"
+dependencies = [
+ "itoa",
+ "memchr",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "sha2"
+version = "0.10.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
+name = "signature"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
+dependencies = [
+ "digest",
+ "rand_core",
+]
+
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
+name = "subtle"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.98"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.98",
+]
+
+[[package]]
+name = "typenum"
+version = "1.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034"
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
+
+[[package]]
+name = "version_check"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "zerocopy"
+version = "0.7.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
+dependencies = [
+ "byteorder",
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.7.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.98",
+]
+
+[[package]]
+name = "zeroize"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
+dependencies = [
+ "zeroize_derive",
+]
+
+[[package]]
+name = "zeroize_derive"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.98",
+]
diff --git a/contracts/ibc2/Cargo.toml b/contracts/ibc2/Cargo.toml
new file mode 100644
index 000000000..4b2b14870
--- /dev/null
+++ b/contracts/ibc2/Cargo.toml
@@ -0,0 +1,28 @@
+[package]
+name = "ibc2"
+version = "0.0.0"
+authors = ["Tomasz Kulik <tomek.k@confio.gmbh>"]
+edition = "2021"
+publish = false
+license = "Apache-2.0"
+
+[lib]
+crate-type = ["cdylib", "rlib"]
+
+[profile.release]
+opt-level = 3
+debug = false
+rpath = false
+lto = true
+debug-assertions = false
+codegen-units = 1
+panic = 'abort'
+incremental = false
+overflow-checks = true
+
+[dependencies]
+cosmwasm-schema = { path = "../../packages/schema" }
+cosmwasm-std = { path = "../../packages/std", features = ["ibc2"] }
+schemars = "0.8.3"
+serde = { version = "1.0.103", default-features = false, features = ["derive"] }
+thiserror = "1.0.26"
diff --git a/contracts/ibc2/schema/ibc2.json b/contracts/ibc2/schema/ibc2.json
new file mode 100644
index 000000000..72cd1703b
--- /dev/null
+++ b/contracts/ibc2/schema/ibc2.json
@@ -0,0 +1,52 @@
+{
+  "contract_name": "ibc2",
+  "contract_version": "0.0.0",
+  "idl_version": "1.0.0",
+  "instantiate": {
+    "$schema": "http://json-schema.org/draft-07/schema#",
+    "title": "InstantiateMsg",
+    "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressible in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)",
+    "type": "object",
+    "additionalProperties": false
+  },
+  "execute": null,
+  "query": {
+    "$schema": "http://json-schema.org/draft-07/schema#",
+    "title": "QueryMsg",
+    "oneOf": [
+      {
+        "type": "object",
+        "required": [
+          "query_state"
+        ],
+        "properties": {
+          "query_state": {
+            "type": "object",
+            "additionalProperties": false
+          }
+        },
+        "additionalProperties": false
+      }
+    ]
+  },
+  "migrate": null,
+  "sudo": null,
+  "responses": {
+    "query_state": {
+      "$schema": "http://json-schema.org/draft-07/schema#",
+      "title": "State",
+      "type": "object",
+      "required": [
+        "ibc2_packet_receive_counter"
+      ],
+      "properties": {
+        "ibc2_packet_receive_counter": {
+          "type": "integer",
+          "format": "uint32",
+          "minimum": 0.0
+        }
+      },
+      "additionalProperties": false
+    }
+  }
+}
diff --git a/contracts/ibc2/schema/raw/instantiate.json b/contracts/ibc2/schema/raw/instantiate.json
new file mode 100644
index 000000000..e12a3fda1
--- /dev/null
+++ b/contracts/ibc2/schema/raw/instantiate.json
@@ -0,0 +1,7 @@
+{
+  "$schema": "http://json-schema.org/draft-07/schema#",
+  "title": "InstantiateMsg",
+  "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressible in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)",
+  "type": "object",
+  "additionalProperties": false
+}
diff --git a/contracts/ibc2/schema/raw/query.json b/contracts/ibc2/schema/raw/query.json
new file mode 100644
index 000000000..60e28f8dd
--- /dev/null
+++ b/contracts/ibc2/schema/raw/query.json
@@ -0,0 +1,19 @@
+{
+  "$schema": "http://json-schema.org/draft-07/schema#",
+  "title": "QueryMsg",
+  "oneOf": [
+    {
+      "type": "object",
+      "required": [
+        "query_state"
+      ],
+      "properties": {
+        "query_state": {
+          "type": "object",
+          "additionalProperties": false
+        }
+      },
+      "additionalProperties": false
+    }
+  ]
+}
diff --git a/contracts/ibc2/schema/raw/response_to_query_state.json b/contracts/ibc2/schema/raw/response_to_query_state.json
new file mode 100644
index 000000000..556c4124d
--- /dev/null
+++ b/contracts/ibc2/schema/raw/response_to_query_state.json
@@ -0,0 +1,16 @@
+{
+  "$schema": "http://json-schema.org/draft-07/schema#",
+  "title": "State",
+  "type": "object",
+  "required": [
+    "ibc2_packet_receive_counter"
+  ],
+  "properties": {
+    "ibc2_packet_receive_counter": {
+      "type": "integer",
+      "format": "uint32",
+      "minimum": 0.0
+    }
+  },
+  "additionalProperties": false
+}
diff --git a/contracts/ibc2/src/bin/schema.rs b/contracts/ibc2/src/bin/schema.rs
new file mode 100644
index 000000000..bdea37223
--- /dev/null
+++ b/contracts/ibc2/src/bin/schema.rs
@@ -0,0 +1,10 @@
+use cosmwasm_schema::write_api;
+use cosmwasm_std::Empty;
+use ibc2::contract::QueryMsg;
+
+fn main() {
+    write_api! {
+        instantiate: Empty,
+        query: QueryMsg,
+    }
+}
diff --git a/contracts/ibc2/src/contract.rs b/contracts/ibc2/src/contract.rs
new file mode 100644
index 000000000..b935466d5
--- /dev/null
+++ b/contracts/ibc2/src/contract.rs
@@ -0,0 +1,68 @@
+use cosmwasm_schema::{cw_serde, QueryResponses};
+use cosmwasm_std::{
+    entry_point, from_json, to_json_vec, Binary, Deps, DepsMut, Empty, Env, Ibc2PacketReceiveMsg,
+    IbcReceiveResponse, MessageInfo, QueryResponse, Response, StdError, StdResult,
+};
+use schemars::JsonSchema;
+use serde::{Deserialize, Serialize};
+
+#[derive(Default, Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
+pub struct State {
+    ibc2_packet_receive_counter: u32,
+}
+
+#[cw_serde]
+#[derive(QueryResponses)]
+pub enum QueryMsg {
+    #[returns(State)]
+    QueryState {},
+}
+
+const STATE_KEY: &[u8] = b"state";
+
+#[entry_point]
+pub fn instantiate(
+    deps: DepsMut,
+    _env: Env,
+    _info: MessageInfo,
+    _msg: Empty,
+) -> StdResult<Response> {
+    deps.storage
+        .set(STATE_KEY, &to_json_vec(&State::default())?);
+
+    Ok(Response::new())
+}
+
+#[entry_point]
+pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<QueryResponse> {
+    match msg {
+        QueryMsg::QueryState {} => {
+            let data = deps
+                .storage
+                .get(STATE_KEY)
+                .ok_or_else(|| StdError::generic_err("State not found."))?;
+            Ok(Binary::from(data))
+        }
+    }
+}
+
+#[entry_point]
+pub fn ibc2_packet_receive(
+    deps: DepsMut,
+    _env: Env,
+    _msg: Ibc2PacketReceiveMsg,
+) -> StdResult<IbcReceiveResponse> {
+    let data = deps
+        .storage
+        .get(STATE_KEY)
+        .ok_or_else(|| StdError::generic_err("State not found."))?;
+    let state: State = from_json(data)?;
+    deps.storage.set(
+        STATE_KEY,
+        &to_json_vec(&State {
+            ibc2_packet_receive_counter: state.ibc2_packet_receive_counter + 1,
+        })?,
+    );
+
+    Ok(IbcReceiveResponse::new([1, 2, 3]))
+}
diff --git a/contracts/ibc2/src/lib.rs b/contracts/ibc2/src/lib.rs
new file mode 100644
index 000000000..2943dbb50
--- /dev/null
+++ b/contracts/ibc2/src/lib.rs
@@ -0,0 +1,1 @@
+pub mod contract;
diff --git a/docs/CAPABILITIES-BUILT-IN.md b/docs/CAPABILITIES-BUILT-IN.md
index 30630b8ca..2335d41f3 100644
--- a/docs/CAPABILITIES-BUILT-IN.md
+++ b/docs/CAPABILITIES-BUILT-IN.md
@@ -11,8 +11,8 @@ might define others.
   "Stargate". It primarily includes protobuf messages and IBC support.
 - `staking` is for chains with the Cosmos SDK staking module. There are Cosmos
   chains that don't use this (e.g. Tgrade).
-- `eureka` is for messages and queries that came with the Cosmos SDK upgrade
-  "Eureka".
+- `ibc2` is for messages and queries that came with the Cosmos SDK upgrade
+  "Ibc2".
 - `cosmwasm_1_1` enables the `BankQuery::Supply` query. Only chains running
   CosmWasm `1.1.0` or higher support this.
 - `cosmwasm_1_2` enables the `GovMsg::VoteWeighted` and `WasmMsg::Instantiate2`
diff --git a/packages/check/src/main.rs b/packages/check/src/main.rs
index e211a405b..818a9ccff 100644
--- a/packages/check/src/main.rs
+++ b/packages/check/src/main.rs
@@ -14,7 +14,7 @@ use cosmwasm_vm::internals::{check_wasm, compile, make_compiling_engine, LogOutp
 use cosmwasm_vm::{capabilities_from_csv, WasmLimits};
 
 const DEFAULT_AVAILABLE_CAPABILITIES: &str =
-    "iterator,staking,stargate,cosmwasm_1_1,cosmwasm_1_2,cosmwasm_1_3,cosmwasm_1_4,cosmwasm_2_0,cosmwasm_2_1,cosmwasm_2_2";
+    "ibc2,iterator,staking,stargate,cosmwasm_1_1,cosmwasm_1_2,cosmwasm_1_3,cosmwasm_1_4,cosmwasm_2_0,cosmwasm_2_1,cosmwasm_2_2";
 
 pub fn main() {
     let matches = Command::new("Contract checking")
diff --git a/packages/std/Cargo.toml b/packages/std/Cargo.toml
index e2a6ab426..0ce109f1d 100644
--- a/packages/std/Cargo.toml
+++ b/packages/std/Cargo.toml
@@ -9,7 +9,7 @@ license = "Apache-2.0"
 readme = "README.md"
 
 [package.metadata.docs.rs]
-features = ["abort", "cosmwasm_2_2", "staking", "stargate", "eureka"]
+features = ["abort", "cosmwasm_2_2", "staking", "stargate", "ibc2"]
 
 [features]
 default = ["iterator", "std"]
@@ -29,8 +29,8 @@ staking = []
 # stargate enables stargate-dependent messages and queries, like raw protobuf messages
 # as well as ibc-related functionality
 stargate = []
-# eureka enables eureka-dependent messages and queries
-eureka = []
+# ibc2 enables ibc2-dependent messages, entrypoints and queries
+ibc2 = []
 # This feature makes `BankQuery::Supply` available for the contract to call, but requires
 # the host blockchain to run CosmWasm `1.1.0` or higher.
 cosmwasm_1_1 = []
diff --git a/packages/std/src/eureka.rs b/packages/std/src/eureka.rs
deleted file mode 100644
index 14c1e916e..000000000
--- a/packages/std/src/eureka.rs
+++ /dev/null
@@ -1,53 +0,0 @@
-use schemars::JsonSchema;
-use serde::{Deserialize, Serialize};
-
-use crate::{Binary, Timestamp};
-
-/// Payload value should be encoded in a format defined by the channel version,
-/// and the module on the other side should know how to parse this.
-#[non_exhaustive]
-#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
-#[serde(rename_all = "snake_case")]
-pub struct EurekaPayload {
-    /// The port id on the chain where the packet is sent to (external chain).
-    pub destination_port: String,
-    /// Version of the receiving contract.
-    pub version: String,
-    /// Encoding used to serialize the [EurekaPayload::value].
-    pub encoding: String,
-    /// Encoded payload data.
-    pub value: Binary,
-}
-
-/// These are messages in the IBC lifecycle using the new Eureka approach. Only usable by IBC-enabled contracts
-#[non_exhaustive]
-#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
-#[serde(rename_all = "snake_case")]
-pub enum EurekaMsg {
-    /// Sends an IBC packet with given payloads over the existing channel.
-    SendPacket {
-        /// existing channel to send the tokens over
-        channel_id: String,
-        timeout: Timestamp,
-        payloads: Vec<EurekaPayload>,
-    },
-}
-
-#[cfg(test)]
-mod tests {
-    use serde_json::to_string;
-
-    use crate::EurekaPayload;
-
-    #[test]
-    fn eureka_payload_serialize() {
-        let packet = EurekaPayload {
-            destination_port: "receiving-contract-port".to_string(),
-            version: "v1".to_string(),
-            encoding: "json".to_string(),
-            value: b"foo".into(),
-        };
-        let expected = r#"{"destination_port":"receiving-contract-port","version":"v1","encoding":"json","value":"Zm9v"}"#;
-        assert_eq!(to_string(&packet).unwrap(), expected);
-    }
-}
diff --git a/packages/std/src/exports.rs b/packages/std/src/exports.rs
index 76d9a4367..023281101 100644
--- a/packages/std/src/exports.rs
+++ b/packages/std/src/exports.rs
@@ -13,13 +13,17 @@ use core::{marker::PhantomData, ptr};
 use serde::de::DeserializeOwned;
 
 use crate::deps::OwnedDeps;
+#[cfg(any(feature = "stargate", feature = "ibc2"))]
+use crate::ibc::IbcReceiveResponse;
 use crate::ibc::{IbcBasicResponse, IbcDestinationCallbackMsg, IbcSourceCallbackMsg};
 #[cfg(feature = "stargate")]
 use crate::ibc::{
     IbcChannelCloseMsg, IbcChannelConnectMsg, IbcPacketAckMsg, IbcPacketReceiveMsg,
-    IbcPacketTimeoutMsg, IbcReceiveResponse,
+    IbcPacketTimeoutMsg,
 };
 use crate::ibc::{IbcChannelOpenMsg, IbcChannelOpenResponse};
+#[cfg(feature = "ibc2")]
+use crate::ibc2::Ibc2PacketReceiveMsg;
 use crate::imports::{ExternalApi, ExternalQuerier, ExternalStorage};
 use crate::memory::{Owned, Region};
 use crate::panic::install_panic_handler;
@@ -44,9 +48,9 @@ extern "C" fn requires_staking() {}
 #[no_mangle]
 extern "C" fn requires_stargate() {}
 
-#[cfg(feature = "eureka")]
+#[cfg(feature = "ibc2")]
 #[no_mangle]
-extern "C" fn requires_eureka() {}
+extern "C" fn requires_ibc2() {}
 
 #[cfg(feature = "cosmwasm_1_1")]
 #[no_mangle]
@@ -522,6 +526,35 @@ where
     Region::from_vec(v).to_heap_ptr() as u32
 }
 
+/// do_ibc2_packet_receive is designed for use with #[entry_point] to make a "C" extern
+///
+/// contract_fn is called when this chain receives an Ibc2 payload on port belonging
+/// to this contract
+///
+/// - `Q`: custom query type (see QueryRequest)
+/// - `C`: custom response message type (see CosmosMsg)
+/// - `E`: error type for responses
+#[cfg(feature = "ibc2")]
+pub fn do_ibc2_packet_receive<Q, C, E>(
+    contract_fn: &dyn Fn(DepsMut<Q>, Env, Ibc2PacketReceiveMsg) -> Result<IbcReceiveResponse<C>, E>,
+    env_ptr: u32,
+    msg_ptr: u32,
+) -> u32
+where
+    Q: CustomQuery,
+    C: CustomMsg,
+    E: ToString,
+{
+    install_panic_handler();
+    let res = _do_ibc2_packet_receive(
+        contract_fn,
+        env_ptr as *mut Region<Owned>,
+        msg_ptr as *mut Region<Owned>,
+    );
+    let v = to_json_vec(&res).unwrap();
+    Region::from_vec(v).to_heap_ptr() as u32
+}
+
 fn _do_instantiate<Q, M, C, E>(
     instantiate_fn: &dyn Fn(DepsMut<Q>, Env, MessageInfo, M) -> Result<Response<C>, E>,
     env_ptr: *mut Region<Owned>,
@@ -889,3 +922,26 @@ where
         custom_query_type: PhantomData,
     }
 }
+
+#[cfg(feature = "ibc2")]
+fn _do_ibc2_packet_receive<Q, C, E>(
+    contract_fn: &dyn Fn(DepsMut<Q>, Env, Ibc2PacketReceiveMsg) -> Result<IbcReceiveResponse<C>, E>,
+    env_ptr: *mut Region<Owned>,
+    msg_ptr: *mut Region<Owned>,
+) -> ContractResult<IbcReceiveResponse<C>>
+where
+    Q: CustomQuery,
+    C: CustomMsg,
+    E: ToString,
+{
+    let env: Vec<u8> =
+        unsafe { Region::from_heap_ptr(ptr::NonNull::new(env_ptr).unwrap()).into_vec() };
+    let msg: Vec<u8> =
+        unsafe { Region::from_heap_ptr(ptr::NonNull::new(msg_ptr).unwrap()).into_vec() };
+
+    let env: Env = try_into_contract_result!(from_json(env));
+    let msg: Ibc2PacketReceiveMsg = try_into_contract_result!(from_json(msg));
+
+    let mut deps = make_dependencies();
+    contract_fn(deps.as_mut(), env, msg).into()
+}
diff --git a/packages/std/src/ibc2.rs b/packages/std/src/ibc2.rs
new file mode 100644
index 000000000..c7450d5d4
--- /dev/null
+++ b/packages/std/src/ibc2.rs
@@ -0,0 +1,75 @@
+use schemars::JsonSchema;
+use serde::{Deserialize, Serialize};
+
+use crate::{Addr, Binary, Timestamp};
+
+/// Payload value should be encoded in a format defined by the channel version,
+/// and the module on the other side should know how to parse this.
+#[non_exhaustive]
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub struct Ibc2Payload {
+    /// The port id on the chain where the packet is sent from.
+    pub source_port: String,
+    /// The port id on the chain where the packet is sent to.
+    pub destination_port: String,
+    /// Version of the receiving contract.
+    pub version: String,
+    /// Encoding used to serialize the [Ibc2Payload::value].
+    pub encoding: String,
+    /// Encoded payload data.
+    pub value: Binary,
+}
+
+/// These are messages in the IBC lifecycle using the new Ibc2 approach.
+/// Only usable by Ibc2-enabled contracts
+#[non_exhaustive]
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum Ibc2Msg {
+    /// Sends an Ibc2 packet with given payloads over the existing channel.
+    SendPacket {
+        channel_id: String,
+        timeout: Timestamp,
+        payloads: Vec<Ibc2Payload>,
+    },
+}
+
+/// The message that is passed into `ibc2_packet_receive`
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
+#[non_exhaustive]
+pub struct Ibc2PacketReceiveMsg {
+    pub payload: Ibc2Payload,
+    pub relayer: Addr,
+    pub source_client: String,
+}
+
+impl Ibc2PacketReceiveMsg {
+    pub fn new(payload: Ibc2Payload, relayer: Addr, source_client: String) -> Self {
+        Self {
+            payload,
+            relayer,
+            source_client,
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use serde_json::to_string;
+
+    use crate::Ibc2Payload;
+
+    #[test]
+    fn ibc2_payload_serialize() {
+        let packet = Ibc2Payload {
+            source_port: "sending-contractr-port".to_string(),
+            destination_port: "receiving-contract-port".to_string(),
+            version: "v1".to_string(),
+            encoding: "json".to_string(),
+            value: b"foo".into(),
+        };
+        let expected = r#"{"source_port":"sending-contractr-port","destination_port":"receiving-contract-port","version":"v1","encoding":"json","value":"Zm9v"}"#;
+        assert_eq!(to_string(&packet).unwrap(), expected);
+    }
+}
diff --git a/packages/std/src/lib.rs b/packages/std/src/lib.rs
index 5f029af69..91a1b01dd 100644
--- a/packages/std/src/lib.rs
+++ b/packages/std/src/lib.rs
@@ -21,10 +21,10 @@ mod conversion;
 mod deps;
 mod encoding;
 mod errors;
-mod eureka;
 mod forward_ref;
 mod hex_binary;
 mod ibc;
+mod ibc2;
 mod import_helpers;
 #[cfg(feature = "iterator")]
 mod iterator;
@@ -68,7 +68,6 @@ pub use crate::errors::{
     RecoverPubkeyError, RoundDownOverflowError, RoundUpOverflowError, StdError, StdResult,
     SystemError, VerificationError,
 };
-pub use crate::eureka::{EurekaMsg, EurekaPayload};
 pub use crate::hex_binary::HexBinary;
 pub use crate::ibc::IbcChannelOpenResponse;
 pub use crate::ibc::{
@@ -79,6 +78,7 @@ pub use crate::ibc::{
     IbcSourceCallbackMsg, IbcSrcCallback, IbcTimeout, IbcTimeoutBlock, IbcTimeoutCallbackMsg,
     TransferMsgBuilder,
 };
+pub use crate::ibc2::{Ibc2Msg, Ibc2PacketReceiveMsg, Ibc2Payload};
 #[cfg(feature = "iterator")]
 pub use crate::iterator::{Order, Record};
 pub use crate::math::{
@@ -137,6 +137,8 @@ mod imports;
 #[cfg(target_arch = "wasm32")]
 mod memory; // Used by exports and imports only. This assumes pointers are 32 bit long, which makes it untestable on dev machines.
 
+#[cfg(all(feature = "ibc2", target_arch = "wasm32"))]
+pub use crate::exports::do_ibc2_packet_receive;
 #[cfg(all(feature = "cosmwasm_2_2", target_arch = "wasm32"))]
 pub use crate::exports::do_migrate_with_info;
 #[cfg(target_arch = "wasm32")]
diff --git a/packages/std/src/query/wasm.rs b/packages/std/src/query/wasm.rs
index de6ac9142..732b7e6aa 100644
--- a/packages/std/src/query/wasm.rs
+++ b/packages/std/src/query/wasm.rs
@@ -44,8 +44,8 @@ pub struct ContractInfoResponse {
     pub pinned: bool,
     /// set if this contract has bound an IBC port
     pub ibc_port: Option<String>,
-    /// set if this contract has bound an Eureka port
-    pub eureka_port: Option<String>,
+    /// set if this contract has bound an Ibc2 port
+    pub ibc2_port: Option<String>,
 }
 
 impl QueryResponseType for ContractInfoResponse {}
@@ -57,7 +57,7 @@ impl_response_constructor!(
     admin: Option<Addr>,
     pinned: bool,
     ibc_port: Option<String>,
-    eureka_port: Option<String>
+    ibc2_port: Option<String>
 );
 
 /// The essential data from wasmd's [CodeInfo]/[CodeInfoResponse].
@@ -122,12 +122,12 @@ mod tests {
             admin: Some(Addr::unchecked("king")),
             pinned: true,
             ibc_port: Some("wasm.123".to_string()),
-            eureka_port: Some("wasm.123".to_string()),
+            ibc2_port: Some("wasm.123".to_string()),
         };
         let json = to_json_binary(&response).unwrap();
         assert_eq!(
             String::from_utf8_lossy(&json),
-            r#"{"code_id":67,"creator":"jane","admin":"king","pinned":true,"ibc_port":"wasm.123","eureka_port":"wasm.123"}"#,
+            r#"{"code_id":67,"creator":"jane","admin":"king","pinned":true,"ibc_port":"wasm.123","ibc2_port":"wasm.123"}"#,
         );
     }
 
diff --git a/packages/std/src/results/cosmos_msg.rs b/packages/std/src/results/cosmos_msg.rs
index 2a06dfba6..ce9729772 100644
--- a/packages/std/src/results/cosmos_msg.rs
+++ b/packages/std/src/results/cosmos_msg.rs
@@ -6,10 +6,10 @@ use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
 
 use crate::coin::Coin;
-#[cfg(feature = "eureka")]
-use crate::eureka::EurekaMsg;
 #[cfg(feature = "stargate")]
 use crate::ibc::IbcMsg;
+#[cfg(feature = "ibc2")]
+use crate::ibc2::Ibc2Msg;
 use crate::prelude::*;
 #[cfg(all(feature = "stargate", feature = "cosmwasm_1_2"))]
 use crate::Decimal;
@@ -86,8 +86,8 @@ pub enum CosmosMsg<T = Empty> {
     Wasm(WasmMsg),
     #[cfg(feature = "stargate")]
     Gov(GovMsg),
-    #[cfg(feature = "eureka")]
-    Eureka(EurekaMsg),
+    #[cfg(feature = "ibc2")]
+    Ibc2(Ibc2Msg),
 }
 
 impl<T> CosmosMsg<T> {
@@ -112,8 +112,8 @@ impl<T> CosmosMsg<T> {
             CosmosMsg::Wasm(msg) => CosmosMsg::Wasm(msg),
             #[cfg(feature = "stargate")]
             CosmosMsg::Gov(msg) => CosmosMsg::Gov(msg),
-            #[cfg(feature = "eureka")]
-            CosmosMsg::Eureka(msg) => CosmosMsg::Eureka(msg),
+            #[cfg(feature = "ibc2")]
+            CosmosMsg::Ibc2(msg) => CosmosMsg::Ibc2(msg),
         })
     }
 }
@@ -491,10 +491,10 @@ impl<T> From<GovMsg> for CosmosMsg<T> {
     }
 }
 
-#[cfg(feature = "eureka")]
-impl<T> From<EurekaMsg> for CosmosMsg<T> {
-    fn from(msg: EurekaMsg) -> Self {
-        CosmosMsg::Eureka(msg)
+#[cfg(feature = "ibc2")]
+impl<T> From<Ibc2Msg> for CosmosMsg<T> {
+    fn from(msg: Ibc2Msg) -> Self {
+        CosmosMsg::Ibc2(msg)
     }
 }
 
diff --git a/packages/std/src/traits.rs b/packages/std/src/traits.rs
index 2a64e1e50..aaadaaba8 100644
--- a/packages/std/src/traits.rs
+++ b/packages/std/src/traits.rs
@@ -737,7 +737,7 @@ mod tests {
                 admin: None,
                 pinned: false,
                 ibc_port: None,
-                eureka_port: None,
+                ibc2_port: None,
             }
         }
 
@@ -769,7 +769,7 @@ mod tests {
                 admin: None,
                 pinned: false,
                 ibc_port: None,
-                eureka_port: None,
+                ibc2_port: None,
             }
         }
 
diff --git a/packages/vm/Cargo.toml b/packages/vm/Cargo.toml
index bc93e6777..b6f7c28f5 100644
--- a/packages/vm/Cargo.toml
+++ b/packages/vm/Cargo.toml
@@ -30,6 +30,8 @@ staking = ["cosmwasm-std/staking"]
 stargate = ["cosmwasm-std/stargate"]
 # For heap profiling. Only used in the "heap_profiling" example.
 dhat-heap = ["dep:dhat"]
+# ibc2 enables ibc2-dependent messages, entrypoints and queries
+ibc2 = ["cosmwasm-std/ibc2"]
 
 [lib]
 # See https://bheisler.github.io/criterion.rs/book/faq.html#cargo-bench-gives-unrecognized-option-errors-for-valid-command-line-options
diff --git a/packages/vm/src/calls.rs b/packages/vm/src/calls.rs
index e2021c9ff..4db041a35 100644
--- a/packages/vm/src/calls.rs
+++ b/packages/vm/src/calls.rs
@@ -5,12 +5,19 @@ use cosmwasm_std::{
     ContractResult, CustomMsg, Env, IbcBasicResponse, IbcDestinationCallbackMsg,
     IbcSourceCallbackMsg, MessageInfo, MigrateInfo, QueryResponse, Reply, Response,
 };
+
+#[cfg(any(feature = "stargate", feature = "ibc2"))]
+use cosmwasm_std::IbcReceiveResponse;
+
 #[cfg(feature = "stargate")]
 use cosmwasm_std::{
     Ibc3ChannelOpenResponse, IbcChannelCloseMsg, IbcChannelConnectMsg, IbcChannelOpenMsg,
-    IbcPacketAckMsg, IbcPacketReceiveMsg, IbcPacketTimeoutMsg, IbcReceiveResponse,
+    IbcPacketAckMsg, IbcPacketReceiveMsg, IbcPacketTimeoutMsg,
 };
 
+#[cfg(feature = "ibc2")]
+use cosmwasm_std::Ibc2PacketReceiveMsg;
+
 use crate::backend::{BackendApi, Querier, Storage};
 use crate::conversion::ref_to_u32;
 use crate::errors::{VmError, VmResult};
@@ -48,7 +55,7 @@ mod read_limits {
     #[cfg(feature = "stargate")]
     pub const RESULT_IBC_CHANNEL_CLOSE: usize = 64 * MI;
     /// Max length (in bytes) of the result data from a ibc_packet_receive call.
-    #[cfg(feature = "stargate")]
+    #[cfg(any(feature = "stargate", feature = "ibc2"))]
     pub const RESULT_IBC_PACKET_RECEIVE: usize = 64 * MI;
     /// Max length (in bytes) of the result data from a ibc_packet_ack call.
     #[cfg(feature = "stargate")]
@@ -91,7 +98,7 @@ mod deserialization_limits {
     #[cfg(feature = "stargate")]
     pub const RESULT_IBC_CHANNEL_CLOSE: usize = 256 * KI;
     /// Max length (in bytes) of the result data from a ibc_packet_receive call.
-    #[cfg(feature = "stargate")]
+    #[cfg(any(feature = "stargate", feature = "ibc2"))]
     pub const RESULT_IBC_PACKET_RECEIVE: usize = 256 * KI;
     /// Max length (in bytes) of the result data from a ibc_packet_ack call.
     #[cfg(feature = "stargate")]
@@ -670,6 +677,26 @@ where
     )
 }
 
+#[cfg(feature = "ibc2")]
+pub fn call_ibc2_packet_receive_raw<A, S, Q>(
+    instance: &mut Instance<A, S, Q>,
+    env: &[u8],
+    msg: &[u8],
+) -> VmResult<Vec<u8>>
+where
+    A: BackendApi + 'static,
+    S: Storage + 'static,
+    Q: Querier + 'static,
+{
+    instance.set_storage_readonly(false);
+    call_raw(
+        instance,
+        "ibc2_packet_receive",
+        &[env, msg],
+        read_limits::RESULT_IBC_PACKET_RECEIVE,
+    )
+}
+
 pub fn call_ibc_source_callback_raw<A, S, Q>(
     instance: &mut Instance<A, S, Q>,
     env: &[u8],
@@ -735,6 +762,25 @@ where
     Ok(data)
 }
 
+#[cfg(feature = "ibc2")]
+pub fn call_ibc2_packet_receive<A, S, Q, U>(
+    instance: &mut Instance<A, S, Q>,
+    env: &Env,
+    msg: &Ibc2PacketReceiveMsg,
+) -> VmResult<ContractResult<IbcReceiveResponse<U>>>
+where
+    A: BackendApi + 'static,
+    S: Storage + 'static,
+    Q: Querier + 'static,
+    U: DeserializeOwned + CustomMsg,
+{
+    let env = to_vec(env)?;
+    let msg = to_vec(msg)?;
+    let data = call_ibc2_packet_receive_raw(instance, &env, &msg)?;
+    let result = from_slice(&data, deserialization_limits::RESULT_IBC_PACKET_RECEIVE)?;
+    Ok(result)
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
@@ -1226,4 +1272,28 @@ mod tests {
             assert_eq!(1, stats.ibc_timeout_callbacks.len());
         }
     }
+
+    #[cfg(feature = "ibc2")]
+    mod ibc2 {
+        use super::*;
+        use cosmwasm_std::testing::mock_ibc2_packet_recv;
+        const CONTRACT: &[u8] = include_bytes!("../testdata/ibc2.wasm");
+
+        #[test]
+        fn call_ibc2_packet_receive_works() {
+            // init
+            let mut instance = mock_instance(CONTRACT, &[]);
+            let info = mock_info("creator", &[]);
+            let instantiate_msg = br#"{}"#;
+            call_instantiate::<_, _, _, Empty>(&mut instance, &mock_env(), &info, instantiate_msg)
+                .unwrap()
+                .unwrap();
+
+            let ibc2_msg = br#"SomeRandomMsg"#;
+            let ibc2_msg = mock_ibc2_packet_recv(ibc2_msg).unwrap();
+            call_ibc2_packet_receive::<_, _, _, Empty>(&mut instance, &mock_env(), &ibc2_msg)
+                .unwrap()
+                .unwrap();
+        }
+    }
 }
diff --git a/packages/vm/src/lib.rs b/packages/vm/src/lib.rs
index e67ea78c8..08db4e795 100644
--- a/packages/vm/src/lib.rs
+++ b/packages/vm/src/lib.rs
@@ -32,6 +32,8 @@ pub use crate::calls::{
     call_migrate_with_info_raw, call_query, call_query_raw, call_reply, call_reply_raw, call_sudo,
     call_sudo_raw,
 };
+#[cfg(feature = "ibc2")]
+pub use crate::calls::{call_ibc2_packet_receive, call_ibc2_packet_receive_raw};
 #[cfg(feature = "stargate")]
 pub use crate::calls::{
     call_ibc_channel_close, call_ibc_channel_close_raw, call_ibc_channel_connect,
diff --git a/packages/vm/src/static_analysis.rs b/packages/vm/src/static_analysis.rs
index f56cbc52b..16a23c4b8 100644
--- a/packages/vm/src/static_analysis.rs
+++ b/packages/vm/src/static_analysis.rs
@@ -33,6 +33,8 @@ pub enum Entrypoint {
     IbcPacketAck,
     #[strum(serialize = "ibc_packet_timeout")]
     IbcPacketTimeout,
+    #[strum(serialize = "ibc2_packet_receive")]
+    Ibc2PacketReceive,
 }
 
 // sort entrypoints by their &str representation


Test patch:
diff --git a/contracts/ibc2/tests/integration.rs b/contracts/ibc2/tests/integration.rs
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/contracts/ibc2/tests/integration.rs
@@ -0,0 +1,1 @@
+
diff --git a/packages/go-gen/tests/cosmwasm_std__ContractInfoResponse.go b/packages/go-gen/tests/cosmwasm_std__ContractInfoResponse.go
index c81b24df6..0365d22e7 100644
--- a/packages/go-gen/tests/cosmwasm_std__ContractInfoResponse.go
+++ b/packages/go-gen/tests/cosmwasm_std__ContractInfoResponse.go
@@ -1,13 +1,13 @@
 type ContractInfoResponse struct {
-    // admin who can run migrations (if any)
-    Admin string `json:"admin,omitempty"`
-    CodeID uint64 `json:"code_id"`
-    // address that instantiated this contract
-    Creator string `json:"creator"`
-    // set if this contract has bound an Eureka port
-    EurekaPort string `json:"eureka_port,omitempty"`
-    // set if this contract has bound an IBC port
-    IBCPort string `json:"ibc_port,omitempty"`
-    // if set, the contract is pinned to the cache, and thus uses less gas when called
-    Pinned bool `json:"pinned"`
-}
+	// admin who can run migrations (if any)
+	Admin  string `json:"admin,omitempty"`
+	CodeID uint64 `json:"code_id"`
+	// address that instantiated this contract
+	Creator string `json:"creator"`
+	// set if this contract has bound an Ibc2 port
+	IBC2Port string `json:"ibc2_port,omitempty"`
+	// set if this contract has bound an IBC port
+	IBCPort string `json:"ibc_port,omitempty"`
+	// if set, the contract is pinned to the cache, and thus uses less gas when called
+	Pinned bool `json:"pinned"`
+}
\ No newline at end of file
diff --git a/packages/std/src/testing/mock.rs b/packages/std/src/testing/mock.rs
index 23cb1d989..ccb3bbe8a 100644
--- a/packages/std/src/testing/mock.rs
+++ b/packages/std/src/testing/mock.rs
@@ -11,7 +11,7 @@ use core::marker::PhantomData;
 use core::ops::Bound;
 use rand_core::OsRng;
 use serde::de::DeserializeOwned;
-#[cfg(feature = "stargate")]
+#[cfg(any(feature = "stargate", feature = "ibc2"))]
 use serde::Serialize;
 use sha2::{Digest, Sha256};
 
@@ -23,6 +23,8 @@ use crate::ibc::{
     IbcEndpoint, IbcOrder, IbcPacket, IbcPacketAckMsg, IbcPacketReceiveMsg, IbcPacketTimeoutMsg,
     IbcTimeoutBlock,
 };
+#[cfg(feature = "ibc2")]
+use crate::ibc2::{Ibc2PacketReceiveMsg, Ibc2Payload};
 #[cfg(feature = "cosmwasm_1_1")]
 use crate::query::SupplyResponse;
 use crate::query::{
@@ -525,6 +527,23 @@ pub fn mock_ibc_packet_recv(
     ))
 }
 
+/// Creates a IbcPacketReceiveMsg for testing ibc_packet_receive. You set a few key parameters that are
+/// often parsed. If you want to set more, use this as a default and mutate other fields
+#[cfg(feature = "ibc2")]
+pub fn mock_ibc2_packet_recv(data: &impl Serialize) -> StdResult<Ibc2PacketReceiveMsg> {
+    Ok(Ibc2PacketReceiveMsg::new(
+        Ibc2Payload {
+            source_port: "wasm2srcport".to_string(),
+            destination_port: "wasm2destport".to_string(),
+            version: "v2".to_string(),
+            encoding: "json".to_string(),
+            value: to_json_binary(data)?,
+        },
+        Addr::unchecked("relayer"),
+        "channel_id23".to_string(),
+    ))
+}
+
 /// Creates a IbcPacket for testing ibc_packet_{ack,timeout}. You set a few key parameters that are
 /// often parsed. If you want to set more, use this as a default and mutate other fields.
 /// The difference from mock_ibc_packet_recv is if `my_channel_id` is src or dest.
@@ -2576,7 +2595,7 @@ mod tests {
                             admin: None,
                             pinned: false,
                             ibc_port: None,
-                            eureka_port: None,
+                            ibc2_port: None,
                         };
                         SystemResult::Ok(ContractResult::Ok(to_json_binary(&response).unwrap()))
                     } else {
@@ -2657,7 +2676,7 @@ mod tests {
         match result {
             SystemResult::Ok(ContractResult::Ok(value)) => assert_eq!(
                 value,
-                br#"{"code_id":4,"creator":"lalala","admin":null,"pinned":false,"ibc_port":null,"eureka_port":null}"#
+                br#"{"code_id":4,"creator":"lalala","admin":null,"pinned":false,"ibc_port":null,"ibc2_port":null}"#
                     as &[u8]
             ),
             res => panic!("Unexpected result: {res:?}"),
diff --git a/packages/std/src/testing/mod.rs b/packages/std/src/testing/mod.rs
index 59f080e37..d77ccbdd0 100644
--- a/packages/std/src/testing/mod.rs
+++ b/packages/std/src/testing/mod.rs
@@ -12,6 +12,8 @@ pub use assertions::assert_approx_eq_impl;
 pub use assertions::assert_hash_works_impl;
 
 pub use message_info::message_info;
+#[cfg(feature = "ibc2")]
+pub use mock::mock_ibc2_packet_recv;
 #[allow(deprecated)]
 pub use mock::mock_info;
 #[cfg(feature = "cosmwasm_1_3")]
diff --git a/packages/vm/src/testing/instance.rs b/packages/vm/src/testing/instance.rs
index 6701cab25..232dcfadc 100644
--- a/packages/vm/src/testing/instance.rs
+++ b/packages/vm/src/testing/instance.rs
@@ -98,7 +98,7 @@ impl MockInstanceOptions<'_> {
     fn default_capabilities() -> HashSet<String> {
         #[allow(unused_mut)]
         let mut out = capabilities_from_csv(
-            "iterator,staking,cosmwasm_1_1,cosmwasm_1_2,cosmwasm_1_3,cosmwasm_1_4,cosmwasm_2_0,cosmwasm_2_1,cosmwasm_2_2",
+            "ibc2,iterator,staking,cosmwasm_1_1,cosmwasm_1_2,cosmwasm_1_3,cosmwasm_1_4,cosmwasm_2_0,cosmwasm_2_1,cosmwasm_2_2",
         );
         #[cfg(feature = "stargate")]
         out.insert("stargate".to_string());