Repository: CosmWasm/cosmwasm
Pull request #1756 (2023-07-03 14:01:02)

Merge 1.3
Merged `release/1.3` into `main`


Patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index e50be631d..d0ee3dcb8 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -86,7 +86,7 @@ workflows:
                 # Add your branch here if benchmarking matters to your work
                 - fix-benchmarking
                 - w3
-      - coverage
+      # - coverage # disabled temporarily because Rust version is too low
   deploy:
     jobs:
       - build_and_upload_devcontracts:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3636d5af4..9cea75c7e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,26 @@ and this project adheres to
 
 ## [Unreleased]
 
+### Changed
+
+- cosmwasm-vm: Avoid using loupe for getting the `Module` size in the file
+  system cache to prepare for the Wasmer 3 upgrade.
+- cosmwasm-vm: When enabling `print_debug` the debug logs are now printed to
+  STDERR instead of STDOUT by default ([#1667]).
+- cosmwasm-vm: Add `Instance::set_debug_handler`/`unset_debug_handler` to allow
+  customizing the handling of debug messages emitted by the contract ([#1667]).
+- cosmwasm-vm: Upgrade Wasmer to version 4.0.0.
+- cosmwasm-check: Update clap dependency to version 4 ([#1677])
+
+[#1667]: https://github.com/CosmWasm/cosmwasm/pull/1667
+[#1677]: https://github.com/CosmWasm/cosmwasm/pull/1677
+
+## [1.3.0-rc.0] - 2023-07-03
+
+### Fixed
+
+- cosmwasm-vm: Add missing cache stats increment when calling `pin`.
+
 ### Added
 
 - cosmwasm-std: Implement `BankQuery::AllDenomMetadata` to allow querying all
@@ -14,47 +34,46 @@ and this project adheres to
   enabled for the `cosmwasm_std` dependency. This makes the contract
   incompatible with chains running anything lower than CosmWasm `1.3.0`.
   ([#1647])
+- cosmwasm-std: Add `DistributionQuery::DelegatorWithdrawAddress`. Also needs
+  the `cosmwasm_1_3` feature (see above). ([#1593])
+- cosmwasm-std: Add `DistributionMsg::FundCommunityPool`. Also needs the
+  `cosmwasm_1_3` feature (see above). ([#1747])
+- cosmwasm-std: Add `FromStr` impl for `Coin`. ([#1684])
+- cosmwasm-std: Add `Coins` helper to handle multiple coins. ([#1687])
 - cosmwasm-vm: Add `Cache::save_wasm_unchecked` to save Wasm blobs that have
   been checked before. This is useful for state-sync where we know the Wasm code
   was checked when it was first uploaded. ([#1635])
-- cosmwasm-std: Add `FromStr` impl for `Coin`. ([#1684])
-- cosmwasm-std: Add `Decimal::bps` and `Decimal256::bps` to create a decimal
-  from a basis point value ([#1715]).
-
+- cosmwasm-vm: Allow sign extension Wasm opcodes in static validation. This
+  allows contracts to be compiled with Rust 1.70.0 and above. ([#1727])
+- cosmwasm-std: Add trait functions `Storage::range_keys` and
+  `Storage::range_values`. The default implementations just use
+  `Storage::range`. Later this can be implemented more efficiently. ([#1748])
+- cosmwasm-std: Add `Int64`, `Int128`, `Int256` and `Int512` signed integer
+  types. ([#1718])
+
+[#1593]: https://github.com/CosmWasm/cosmwasm/pull/1593
 [#1635]: https://github.com/CosmWasm/cosmwasm/pull/1635
 [#1647]: https://github.com/CosmWasm/cosmwasm/pull/1647
 [#1684]: https://github.com/CosmWasm/cosmwasm/pull/1684
-[#1715]: https://github.com/CosmWasm/cosmwasm/pull/1715
+[#1687]: https://github.com/CosmWasm/cosmwasm/pull/1687
+[#1718]: https://github.com/CosmWasm/cosmwasm/pull/1718
+[#1727]: https://github.com/CosmWasm/cosmwasm/issues/1727
+[#1747]: https://github.com/CosmWasm/cosmwasm/pull/1747
+[#1748]: https://github.com/CosmWasm/cosmwasm/pull/1748
 
 ### Changed
 
 - cosmwasm-vm: Add checks for table section of Wasm blob ([#1631]).
 - cosmwasm-vm: Limit number of imports during static validation ([#1629]).
-- cosmwasm-vm: The `check_contract` example was removed. Please use the crate
-  [cosmwasm-check](https://crates.io/crates/cosmwasm-check) instead ([#1511]).
-- cosmwasm-vm: Avoid using loupe for getting the `Module` size in the file
-  system cache to prepare for the Wasmer 3 upgrade.
 - cosmwasm-vm: Add target (triple + CPU features) into the module cache
   directory to avoid using modules compiled for a different system. Bump
-  `MODULE_SERIALIZATION_VERSION` to "v5". ([#1664])
-- cosmwasm-vm: When enabling `print_debug` the debug logs are now printed to
-  STDERR instead of STDOUT by default ([#1667]).
-- cosmwasm-vm: Add `Instance::set_debug_handler`/`unset_debug_handler` to allow
-  customizing the handling of debug messages emitted by the contract ([#1667]).
+  `MODULE_SERIALIZATION_VERSION` to "v6". ([#1664])
 - cosmwasm-vm: Add `.wasm` extension to stored wasm files ([#1686]).
-- cosmwasm-vm: Upgrade Wasmer to version 4.0.0.
-- cosmwasm-check: Update clap dependency to version 4 ([#1677])
-- cosmwasm-std: Coin uses shorter `Coin { 123 "ucosm" }` format for Debug
-  ([#1704])
 
-[#1511]: https://github.com/CosmWasm/cosmwasm/issues/1511
 [#1629]: https://github.com/CosmWasm/cosmwasm/pull/1629
 [#1631]: https://github.com/CosmWasm/cosmwasm/pull/1631
 [#1664]: https://github.com/CosmWasm/cosmwasm/pull/1664
-[#1667]: https://github.com/CosmWasm/cosmwasm/pull/1667
-[#1677]: https://github.com/CosmWasm/cosmwasm/pull/1677
 [#1686]: https://github.com/CosmWasm/cosmwasm/pull/1686
-[#1704]: https://github.com/CosmWasm/cosmwasm/pull/1704
 
 ### Deprecated
 
@@ -1773,7 +1792,8 @@ Some main points:
 
 All future Changelog entries will reference this base
 
-[unreleased]: https://github.com/CosmWasm/cosmwasm/compare/v1.2.7...HEAD
+[unreleased]: https://github.com/CosmWasm/cosmwasm/compare/v1.3.0-rc.0...HEAD
+[1.3.0-rc.0]: https://github.com/CosmWasm/cosmwasm/compare/v1.2.7...v1.3.0-rc.0
 [1.2.7]: https://github.com/CosmWasm/cosmwasm/compare/v1.2.6...v1.2.7
 [1.2.6]: https://github.com/CosmWasm/cosmwasm/compare/v1.2.5...v1.2.6
 [1.2.5]: https://github.com/CosmWasm/cosmwasm/compare/v1.2.4...v1.2.5
diff --git a/Cargo.lock b/Cargo.lock
index 81191e1ff..0071b3775 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -211,6 +211,12 @@ dependencies = [
  "generic-array",
 ]
 
+[[package]]
+name = "bnum"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "845141a4fade3f790628b7daaaa298a25b204fb28907eb54febe5142db6ce653"
+
 [[package]]
 name = "bstr"
 version = "1.5.0"
@@ -411,7 +417,7 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-check"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "anyhow",
  "assert_cmd",
@@ -424,7 +430,7 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-crypto"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "base64",
  "criterion",
@@ -443,7 +449,7 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-derive"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "cosmwasm-std",
  "syn 1.0.109",
@@ -451,7 +457,7 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-schema"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "anyhow",
  "cosmwasm-schema-derive",
@@ -466,7 +472,7 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-schema-derive"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -475,9 +481,10 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-std"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "base64",
+ "bnum",
  "chrono",
  "cosmwasm-crypto",
  "cosmwasm-derive",
@@ -492,12 +499,11 @@ dependencies = [
  "serde_json",
  "sha2 0.10.6",
  "thiserror",
- "uint",
 ]
 
 [[package]]
 name = "cosmwasm-storage"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "cosmwasm-std",
  "serde",
@@ -505,7 +511,7 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-vm"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "bitflags",
  "bytecheck",
@@ -716,12 +722,6 @@ dependencies = [
  "cfg-if",
 ]
 
-[[package]]
-name = "crunchy"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
-
 [[package]]
 name = "crypto-bigint"
 version = "0.4.9"
@@ -2015,12 +2015,6 @@ version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
 
-[[package]]
-name = "static_assertions"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
-
 [[package]]
 name = "strsim"
 version = "0.10.0"
@@ -2175,18 +2169,6 @@ version = "1.16.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
 
-[[package]]
-name = "uint"
-version = "0.9.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52"
-dependencies = [
- "byteorder",
- "crunchy",
- "hex",
- "static_assertions",
-]
-
 [[package]]
 name = "unicode-bidi"
 version = "0.3.13"
diff --git a/contracts/burner/Cargo.lock b/contracts/burner/Cargo.lock
index b4398eeab..e7afa66cb 100644
--- a/contracts/burner/Cargo.lock
+++ b/contracts/burner/Cargo.lock
@@ -97,6 +97,12 @@ dependencies = [
  "generic-array",
 ]
 
+[[package]]
+name = "bnum"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "845141a4fade3f790628b7daaaa298a25b204fb28907eb54febe5142db6ce653"
+
 [[package]]
 name = "bumpalo"
 version = "3.12.0"
@@ -187,7 +193,7 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-crypto"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "digest 0.10.3",
  "ed25519-zebra",
@@ -198,14 +204,14 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-derive"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "syn",
 ]
 
 [[package]]
 name = "cosmwasm-schema"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "cosmwasm-schema-derive",
  "schemars",
@@ -216,7 +222,7 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-schema-derive"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -225,9 +231,10 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-std"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "base64",
+ "bnum",
  "cosmwasm-crypto",
  "cosmwasm-derive",
  "derivative",
@@ -238,12 +245,11 @@ dependencies = [
  "serde-json-wasm",
  "sha2 0.10.3",
  "thiserror",
- "uint",
 ]
 
 [[package]]
 name = "cosmwasm-vm"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "bitflags",
  "bytecheck",
@@ -419,12 +425,6 @@ dependencies = [
  "lazy_static",
 ]
 
-[[package]]
-name = "crunchy"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
-
 [[package]]
 name = "crypto-bigint"
 version = "0.4.8"
@@ -1410,12 +1410,6 @@ version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
 
-[[package]]
-name = "static_assertions"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
-
 [[package]]
 name = "strsim"
 version = "0.10.0"
@@ -1518,18 +1512,6 @@ version = "1.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
 
-[[package]]
-name = "uint"
-version = "0.9.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0"
-dependencies = [
- "byteorder",
- "crunchy",
- "hex",
- "static_assertions",
-]
-
 [[package]]
 name = "unicode-bidi"
 version = "0.3.13"
diff --git a/contracts/crypto-verify/Cargo.lock b/contracts/crypto-verify/Cargo.lock
index 8830044d6..bdf504794 100644
--- a/contracts/crypto-verify/Cargo.lock
+++ b/contracts/crypto-verify/Cargo.lock
@@ -97,6 +97,12 @@ dependencies = [
  "generic-array",
 ]
 
+[[package]]
+name = "bnum"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "845141a4fade3f790628b7daaaa298a25b204fb28907eb54febe5142db6ce653"
+
 [[package]]
 name = "bumpalo"
 version = "3.12.0"
@@ -176,7 +182,7 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-crypto"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "digest 0.10.3",
  "ed25519-zebra",
@@ -187,14 +193,14 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-derive"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "syn",
 ]
 
 [[package]]
 name = "cosmwasm-schema"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "cosmwasm-schema-derive",
  "schemars",
@@ -205,7 +211,7 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-schema-derive"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -214,9 +220,10 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-std"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "base64",
+ "bnum",
  "cosmwasm-crypto",
  "cosmwasm-derive",
  "derivative",
@@ -227,12 +234,11 @@ dependencies = [
  "serde-json-wasm",
  "sha2 0.10.3",
  "thiserror",
- "uint",
 ]
 
 [[package]]
 name = "cosmwasm-vm"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "bitflags",
  "bytecheck",
@@ -408,12 +414,6 @@ dependencies = [
  "lazy_static",
 ]
 
-[[package]]
-name = "crunchy"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
-
 [[package]]
 name = "crypto-bigint"
 version = "0.4.8"
@@ -1453,12 +1453,6 @@ version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
 
-[[package]]
-name = "static_assertions"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
-
 [[package]]
 name = "strsim"
 version = "0.10.0"
@@ -1561,18 +1555,6 @@ version = "1.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
 
-[[package]]
-name = "uint"
-version = "0.9.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0"
-dependencies = [
- "byteorder",
- "crunchy",
- "hex",
- "static_assertions",
-]
-
 [[package]]
 name = "unicode-bidi"
 version = "0.3.13"
diff --git a/contracts/cyberpunk/Cargo.lock b/contracts/cyberpunk/Cargo.lock
index acf7eb5ad..486f71732 100644
--- a/contracts/cyberpunk/Cargo.lock
+++ b/contracts/cyberpunk/Cargo.lock
@@ -120,6 +120,12 @@ dependencies = [
  "generic-array",
 ]
 
+[[package]]
+name = "bnum"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "845141a4fade3f790628b7daaaa298a25b204fb28907eb54febe5142db6ce653"
+
 [[package]]
 name = "bumpalo"
 version = "3.12.0"
@@ -205,7 +211,7 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-crypto"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "digest 0.10.3",
  "ed25519-zebra",
@@ -216,14 +222,14 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-derive"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "syn",
 ]
 
 [[package]]
 name = "cosmwasm-schema"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "cosmwasm-schema-derive",
  "schemars",
@@ -234,7 +240,7 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-schema-derive"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -243,9 +249,10 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-std"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "base64",
+ "bnum",
  "cosmwasm-crypto",
  "cosmwasm-derive",
  "derivative",
@@ -256,12 +263,11 @@ dependencies = [
  "serde-json-wasm",
  "sha2 0.10.3",
  "thiserror",
- "uint",
 ]
 
 [[package]]
 name = "cosmwasm-vm"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "bitflags",
  "bytecheck",
@@ -437,12 +443,6 @@ dependencies = [
  "lazy_static",
 ]
 
-[[package]]
-name = "crunchy"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
-
 [[package]]
 name = "crypto-bigint"
 version = "0.4.8"
@@ -1537,12 +1537,6 @@ version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
 
-[[package]]
-name = "static_assertions"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
-
 [[package]]
 name = "strsim"
 version = "0.10.0"
@@ -1658,18 +1652,6 @@ version = "1.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
 
-[[package]]
-name = "uint"
-version = "0.9.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0"
-dependencies = [
- "byteorder",
- "crunchy",
- "hex",
- "static_assertions",
-]
-
 [[package]]
 name = "unicode-bidi"
 version = "0.3.13"
diff --git a/contracts/floaty/Cargo.lock b/contracts/floaty/Cargo.lock
index 00d8db842..7e88d1d53 100644
--- a/contracts/floaty/Cargo.lock
+++ b/contracts/floaty/Cargo.lock
@@ -97,6 +97,12 @@ dependencies = [
  "generic-array",
 ]
 
+[[package]]
+name = "bnum"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "845141a4fade3f790628b7daaaa298a25b204fb28907eb54febe5142db6ce653"
+
 [[package]]
 name = "bumpalo"
 version = "3.12.0"
@@ -176,7 +182,7 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-crypto"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "digest 0.10.3",
  "ed25519-zebra",
@@ -187,14 +193,14 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-derive"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "syn",
 ]
 
 [[package]]
 name = "cosmwasm-schema"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "cosmwasm-schema-derive",
  "schemars",
@@ -205,7 +211,7 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-schema-derive"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -214,9 +220,10 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-std"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "base64",
+ "bnum",
  "cosmwasm-crypto",
  "cosmwasm-derive",
  "derivative",
@@ -227,12 +234,11 @@ dependencies = [
  "serde-json-wasm",
  "sha2 0.10.3",
  "thiserror",
- "uint",
 ]
 
 [[package]]
 name = "cosmwasm-vm"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "bitflags",
  "bytecheck",
@@ -408,12 +414,6 @@ dependencies = [
  "lazy_static",
 ]
 
-[[package]]
-name = "crunchy"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
-
 [[package]]
 name = "crypto-bigint"
 version = "0.4.8"
@@ -1411,12 +1411,6 @@ version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
 
-[[package]]
-name = "static_assertions"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
-
 [[package]]
 name = "strsim"
 version = "0.10.0"
@@ -1519,18 +1513,6 @@ version = "1.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
 
-[[package]]
-name = "uint"
-version = "0.9.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0"
-dependencies = [
- "byteorder",
- "crunchy",
- "hex",
- "static_assertions",
-]
-
 [[package]]
 name = "unicode-bidi"
 version = "0.3.13"
diff --git a/contracts/hackatom/Cargo.lock b/contracts/hackatom/Cargo.lock
index 266b9315b..2c8549bc2 100644
--- a/contracts/hackatom/Cargo.lock
+++ b/contracts/hackatom/Cargo.lock
@@ -97,6 +97,12 @@ dependencies = [
  "generic-array",
 ]
 
+[[package]]
+name = "bnum"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "845141a4fade3f790628b7daaaa298a25b204fb28907eb54febe5142db6ce653"
+
 [[package]]
 name = "bumpalo"
 version = "3.12.0"
@@ -176,7 +182,7 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-crypto"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "digest 0.10.3",
  "ed25519-zebra",
@@ -187,14 +193,14 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-derive"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "syn",
 ]
 
 [[package]]
 name = "cosmwasm-schema"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "cosmwasm-schema-derive",
  "schemars",
@@ -205,7 +211,7 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-schema-derive"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -214,9 +220,10 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-std"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "base64",
+ "bnum",
  "cosmwasm-crypto",
  "cosmwasm-derive",
  "derivative",
@@ -227,12 +234,11 @@ dependencies = [
  "serde-json-wasm",
  "sha2 0.10.3",
  "thiserror",
- "uint",
 ]
 
 [[package]]
 name = "cosmwasm-vm"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "bitflags",
  "bytecheck",
@@ -408,12 +414,6 @@ dependencies = [
  "lazy_static",
 ]
 
-[[package]]
-name = "crunchy"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
-
 [[package]]
 name = "crypto-bigint"
 version = "0.4.8"
@@ -1412,12 +1412,6 @@ version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
 
-[[package]]
-name = "static_assertions"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
-
 [[package]]
 name = "strsim"
 version = "0.10.0"
@@ -1520,18 +1514,6 @@ version = "1.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
 
-[[package]]
-name = "uint"
-version = "0.9.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0"
-dependencies = [
- "byteorder",
- "crunchy",
- "hex",
- "static_assertions",
-]
-
 [[package]]
 name = "unicode-bidi"
 version = "0.3.13"
diff --git a/contracts/ibc-reflect-send/Cargo.lock b/contracts/ibc-reflect-send/Cargo.lock
index 4c187f772..b8765ec7a 100644
--- a/contracts/ibc-reflect-send/Cargo.lock
+++ b/contracts/ibc-reflect-send/Cargo.lock
@@ -97,6 +97,12 @@ dependencies = [
  "generic-array",
 ]
 
+[[package]]
+name = "bnum"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "845141a4fade3f790628b7daaaa298a25b204fb28907eb54febe5142db6ce653"
+
 [[package]]
 name = "bumpalo"
 version = "3.12.0"
@@ -176,7 +182,7 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-crypto"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "digest 0.10.3",
  "ed25519-zebra",
@@ -187,14 +193,14 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-derive"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "syn",
 ]
 
 [[package]]
 name = "cosmwasm-schema"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "cosmwasm-schema-derive",
  "schemars",
@@ -205,7 +211,7 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-schema-derive"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -214,9 +220,10 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-std"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "base64",
+ "bnum",
  "cosmwasm-crypto",
  "cosmwasm-derive",
  "derivative",
@@ -227,12 +234,11 @@ dependencies = [
  "serde-json-wasm",
  "sha2 0.10.3",
  "thiserror",
- "uint",
 ]
 
 [[package]]
 name = "cosmwasm-vm"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "bitflags",
  "bytecheck",
@@ -408,12 +414,6 @@ dependencies = [
  "lazy_static",
 ]
 
-[[package]]
-name = "crunchy"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
-
 [[package]]
 name = "crypto-bigint"
 version = "0.4.8"
@@ -1410,12 +1410,6 @@ version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
 
-[[package]]
-name = "static_assertions"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
-
 [[package]]
 name = "strsim"
 version = "0.10.0"
@@ -1518,18 +1512,6 @@ version = "1.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
 
-[[package]]
-name = "uint"
-version = "0.9.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0"
-dependencies = [
- "byteorder",
- "crunchy",
- "hex",
- "static_assertions",
-]
-
 [[package]]
 name = "unicode-bidi"
 version = "0.3.13"
diff --git a/contracts/ibc-reflect/Cargo.lock b/contracts/ibc-reflect/Cargo.lock
index 97100109e..7c3927f3b 100644
--- a/contracts/ibc-reflect/Cargo.lock
+++ b/contracts/ibc-reflect/Cargo.lock
@@ -97,6 +97,12 @@ dependencies = [
  "generic-array",
 ]
 
+[[package]]
+name = "bnum"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "845141a4fade3f790628b7daaaa298a25b204fb28907eb54febe5142db6ce653"
+
 [[package]]
 name = "bumpalo"
 version = "3.12.0"
@@ -176,7 +182,7 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-crypto"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "digest 0.10.3",
  "ed25519-zebra",
@@ -187,14 +193,14 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-derive"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "syn",
 ]
 
 [[package]]
 name = "cosmwasm-schema"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "cosmwasm-schema-derive",
  "schemars",
@@ -205,7 +211,7 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-schema-derive"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -214,9 +220,10 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-std"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "base64",
+ "bnum",
  "cosmwasm-crypto",
  "cosmwasm-derive",
  "derivative",
@@ -227,12 +234,11 @@ dependencies = [
  "serde-json-wasm",
  "sha2 0.10.3",
  "thiserror",
- "uint",
 ]
 
 [[package]]
 name = "cosmwasm-vm"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "bitflags",
  "bytecheck",
@@ -408,12 +414,6 @@ dependencies = [
  "lazy_static",
 ]
 
-[[package]]
-name = "crunchy"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
-
 [[package]]
 name = "crypto-bigint"
 version = "0.4.8"
@@ -1410,12 +1410,6 @@ version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
 
-[[package]]
-name = "static_assertions"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
-
 [[package]]
 name = "strsim"
 version = "0.10.0"
@@ -1518,18 +1512,6 @@ version = "1.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
 
-[[package]]
-name = "uint"
-version = "0.9.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0"
-dependencies = [
- "byteorder",
- "crunchy",
- "hex",
- "static_assertions",
-]
-
 [[package]]
 name = "unicode-bidi"
 version = "0.3.13"
diff --git a/contracts/queue/Cargo.lock b/contracts/queue/Cargo.lock
index 030268b6a..2d39bd5af 100644
--- a/contracts/queue/Cargo.lock
+++ b/contracts/queue/Cargo.lock
@@ -97,6 +97,12 @@ dependencies = [
  "generic-array",
 ]
 
+[[package]]
+name = "bnum"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "845141a4fade3f790628b7daaaa298a25b204fb28907eb54febe5142db6ce653"
+
 [[package]]
 name = "bumpalo"
 version = "3.12.0"
@@ -176,7 +182,7 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-crypto"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "digest 0.10.3",
  "ed25519-zebra",
@@ -187,14 +193,14 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-derive"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "syn",
 ]
 
 [[package]]
 name = "cosmwasm-schema"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "cosmwasm-schema-derive",
  "schemars",
@@ -205,7 +211,7 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-schema-derive"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -214,9 +220,10 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-std"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "base64",
+ "bnum",
  "cosmwasm-crypto",
  "cosmwasm-derive",
  "derivative",
@@ -227,12 +234,11 @@ dependencies = [
  "serde-json-wasm",
  "sha2 0.10.3",
  "thiserror",
- "uint",
 ]
 
 [[package]]
 name = "cosmwasm-vm"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "bitflags",
  "bytecheck",
@@ -408,12 +414,6 @@ dependencies = [
  "lazy_static",
 ]
 
-[[package]]
-name = "crunchy"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
-
 [[package]]
 name = "crypto-bigint"
 version = "0.4.8"
@@ -1410,12 +1410,6 @@ version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
 
-[[package]]
-name = "static_assertions"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
-
 [[package]]
 name = "strsim"
 version = "0.10.0"
@@ -1518,18 +1512,6 @@ version = "1.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
 
-[[package]]
-name = "uint"
-version = "0.9.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0"
-dependencies = [
- "byteorder",
- "crunchy",
- "hex",
- "static_assertions",
-]
-
 [[package]]
 name = "unicode-bidi"
 version = "0.3.13"
diff --git a/contracts/reflect/Cargo.lock b/contracts/reflect/Cargo.lock
index e5d5e5186..742fb8f91 100644
--- a/contracts/reflect/Cargo.lock
+++ b/contracts/reflect/Cargo.lock
@@ -97,6 +97,12 @@ dependencies = [
  "generic-array",
 ]
 
+[[package]]
+name = "bnum"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "845141a4fade3f790628b7daaaa298a25b204fb28907eb54febe5142db6ce653"
+
 [[package]]
 name = "bumpalo"
 version = "3.12.0"
@@ -176,7 +182,7 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-crypto"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "digest 0.10.3",
  "ed25519-zebra",
@@ -187,14 +193,14 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-derive"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "syn",
 ]
 
 [[package]]
 name = "cosmwasm-schema"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "cosmwasm-schema-derive",
  "schemars",
@@ -205,7 +211,7 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-schema-derive"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -214,9 +220,10 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-std"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "base64",
+ "bnum",
  "cosmwasm-crypto",
  "cosmwasm-derive",
  "derivative",
@@ -227,12 +234,11 @@ dependencies = [
  "serde-json-wasm",
  "sha2 0.10.3",
  "thiserror",
- "uint",
 ]
 
 [[package]]
 name = "cosmwasm-vm"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "bitflags",
  "bytecheck",
@@ -408,12 +414,6 @@ dependencies = [
  "lazy_static",
 ]
 
-[[package]]
-name = "crunchy"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
-
 [[package]]
 name = "crypto-bigint"
 version = "0.4.8"
@@ -1411,12 +1411,6 @@ version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
 
-[[package]]
-name = "static_assertions"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
-
 [[package]]
 name = "strsim"
 version = "0.10.0"
@@ -1519,18 +1513,6 @@ version = "1.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
 
-[[package]]
-name = "uint"
-version = "0.9.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0"
-dependencies = [
- "byteorder",
- "crunchy",
- "hex",
- "static_assertions",
-]
-
 [[package]]
 name = "unicode-bidi"
 version = "0.3.13"
diff --git a/contracts/staking/Cargo.lock b/contracts/staking/Cargo.lock
index 9677d2196..eb507bdb2 100644
--- a/contracts/staking/Cargo.lock
+++ b/contracts/staking/Cargo.lock
@@ -97,6 +97,12 @@ dependencies = [
  "generic-array",
 ]
 
+[[package]]
+name = "bnum"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "845141a4fade3f790628b7daaaa298a25b204fb28907eb54febe5142db6ce653"
+
 [[package]]
 name = "bumpalo"
 version = "3.12.0"
@@ -176,7 +182,7 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-crypto"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "digest 0.10.3",
  "ed25519-zebra",
@@ -187,14 +193,14 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-derive"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "syn",
 ]
 
 [[package]]
 name = "cosmwasm-schema"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "cosmwasm-schema-derive",
  "schemars",
@@ -205,7 +211,7 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-schema-derive"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -214,9 +220,10 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-std"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "base64",
+ "bnum",
  "cosmwasm-crypto",
  "cosmwasm-derive",
  "derivative",
@@ -227,12 +234,11 @@ dependencies = [
  "serde-json-wasm",
  "sha2 0.10.3",
  "thiserror",
- "uint",
 ]
 
 [[package]]
 name = "cosmwasm-vm"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "bitflags",
  "bytecheck",
@@ -408,12 +414,6 @@ dependencies = [
  "lazy_static",
 ]
 
-[[package]]
-name = "crunchy"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
-
 [[package]]
 name = "crypto-bigint"
 version = "0.4.8"
@@ -1438,12 +1438,6 @@ dependencies = [
  "snafu",
 ]
 
-[[package]]
-name = "static_assertions"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
-
 [[package]]
 name = "strsim"
 version = "0.10.0"
@@ -1546,18 +1540,6 @@ version = "1.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
 
-[[package]]
-name = "uint"
-version = "0.9.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0"
-dependencies = [
- "byteorder",
- "crunchy",
- "hex",
- "static_assertions",
-]
-
 [[package]]
 name = "unicode-bidi"
 version = "0.3.13"
diff --git a/contracts/virus/Cargo.lock b/contracts/virus/Cargo.lock
index cbf31b6a6..2bf601883 100644
--- a/contracts/virus/Cargo.lock
+++ b/contracts/virus/Cargo.lock
@@ -97,6 +97,12 @@ dependencies = [
  "generic-array",
 ]
 
+[[package]]
+name = "bnum"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "845141a4fade3f790628b7daaaa298a25b204fb28907eb54febe5142db6ce653"
+
 [[package]]
 name = "bumpalo"
 version = "3.12.0"
@@ -176,7 +182,7 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-crypto"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "digest 0.10.3",
  "ed25519-zebra",
@@ -187,14 +193,14 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-derive"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "syn",
 ]
 
 [[package]]
 name = "cosmwasm-schema"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "cosmwasm-schema-derive",
  "schemars",
@@ -205,7 +211,7 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-schema-derive"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -214,9 +220,10 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-std"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "base64",
+ "bnum",
  "cosmwasm-crypto",
  "cosmwasm-derive",
  "derivative",
@@ -227,12 +234,11 @@ dependencies = [
  "serde-json-wasm",
  "sha2 0.10.3",
  "thiserror",
- "uint",
 ]
 
 [[package]]
 name = "cosmwasm-vm"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 dependencies = [
  "bitflags",
  "bytecheck",
@@ -408,12 +414,6 @@ dependencies = [
  "lazy_static",
 ]
 
-[[package]]
-name = "crunchy"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
-
 [[package]]
 name = "crypto-bigint"
 version = "0.4.8"
@@ -1399,12 +1399,6 @@ version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
 
-[[package]]
-name = "static_assertions"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
-
 [[package]]
 name = "strsim"
 version = "0.10.0"
@@ -1507,18 +1501,6 @@ version = "1.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
 
-[[package]]
-name = "uint"
-version = "0.9.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0"
-dependencies = [
- "byteorder",
- "crunchy",
- "hex",
- "static_assertions",
-]
-
 [[package]]
 name = "unicode-bidi"
 version = "0.3.13"
diff --git a/docs/CAPABILITIES-BUILT-IN.md b/docs/CAPABILITIES-BUILT-IN.md
index d82297180..2f2268e4e 100644
--- a/docs/CAPABILITIES-BUILT-IN.md
+++ b/docs/CAPABILITIES-BUILT-IN.md
@@ -15,6 +15,7 @@ might define others.
   CosmWasm `1.1.0` or higher support this.
 - `cosmwasm_1_2` enables the `GovMsg::VoteWeighted` and `WasmMsg::Instantiate2`
   messages. Only chains running CosmWasm `1.2.0` or higher support this.
-- `cosmwasm_1_3` enables the `BankQuery::AllDenomMetadata` and
-  `BankQuery::DenomMetadata` queries. Only chains running CosmWasm `1.3.0` or
-  higher support this.
+- `cosmwasm_1_3` enables the `BankQuery::AllDenomMetadata`,
+  `BankQuery::DenomMetadata` and `DistributionQuery::DelegatorWithdrawAddress`
+  queries, as well as `DistributionMsg::FundCommunityPool`. Only chains running
+  CosmWasm `1.3.0` or higher support this.
diff --git a/packages/check/Cargo.toml b/packages/check/Cargo.toml
index ed6c920cf..88300df19 100644
--- a/packages/check/Cargo.toml
+++ b/packages/check/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "cosmwasm-check"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 authors = ["Mauro Lacy <mauro@lacy.com.es>"]
 edition = "2021"
 description = "A CLI tool for verifying CosmWasm smart contracts"
@@ -11,8 +11,8 @@ license = "Apache-2.0"
 anyhow = "1.0.57"
 clap = "4"
 colored = "2"
-cosmwasm-vm = { path = "../vm", version = "1.2.7" }
-cosmwasm-std = { path = "../std", version = "1.2.7" }
+cosmwasm-vm = { path = "../vm", version = "1.3.0-rc.0" }
+cosmwasm-std = { path = "../std", version = "1.3.0-rc.0" }
 
 [dev-dependencies]
 assert_cmd = "=2.0.10" # 2.0.11+ requires Rust 1.65.0 which we currently don't want to make the minimum if possible
diff --git a/packages/crypto/Cargo.toml b/packages/crypto/Cargo.toml
index e5e16090a..c1a3d9b10 100644
--- a/packages/crypto/Cargo.toml
+++ b/packages/crypto/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "cosmwasm-crypto"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 authors = ["Mauro Lacy <maurolacy@users.noreply.github.com>"]
 edition = "2021"
 description = "Crypto bindings for cosmwasm contracts"
diff --git a/packages/derive/Cargo.toml b/packages/derive/Cargo.toml
index c6ab3d563..68212e10a 100644
--- a/packages/derive/Cargo.toml
+++ b/packages/derive/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "cosmwasm-derive"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 authors = ["Simon Warta <webmaster128@users.noreply.github.com>"]
 edition = "2021"
 description = "A package for auto-generated code used for CosmWasm contract development. This is shipped as part of cosmwasm-std. Do not use directly."
diff --git a/packages/schema-derive/Cargo.toml b/packages/schema-derive/Cargo.toml
index 07b76b970..ccf9e6ce0 100644
--- a/packages/schema-derive/Cargo.toml
+++ b/packages/schema-derive/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "cosmwasm-schema-derive"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 authors = ["Tomasz Kurcz <tom@confio.gmbh>"]
 edition = "2021"
 description = "Derive macros for cosmwasm-schema"
diff --git a/packages/schema/Cargo.toml b/packages/schema/Cargo.toml
index 153beae69..7394ecb89 100644
--- a/packages/schema/Cargo.toml
+++ b/packages/schema/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "cosmwasm-schema"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 authors = ["Simon Warta <webmaster128@users.noreply.github.com>", "Ethan Frey <ethanfrey@users.noreply.github.com>"]
 edition = "2021"
 description = "A dev-dependency for CosmWasm contracts to generate JSON Schema files."
@@ -8,7 +8,7 @@ repository = "https://github.com/CosmWasm/cosmwasm/tree/main/packages/schema"
 license = "Apache-2.0"
 
 [dependencies]
-cosmwasm-schema-derive = { version = "=1.2.7", path = "../schema-derive" }
+cosmwasm-schema-derive = { version = "=1.3.0-rc.0", path = "../schema-derive" }
 schemars = "0.8.3"
 serde = "1.0"
 serde_json = "1.0.40"
@@ -16,6 +16,6 @@ thiserror = "1.0.26"
 
 [dev-dependencies]
 anyhow = "1.0.57"
-cosmwasm-std = { version = "1.2.7", path = "../std" }
+cosmwasm-std = { version = "1.3.0-rc.0", path = "../std" }
 semver = "1"
 tempfile = "3"
diff --git a/packages/std/Cargo.toml b/packages/std/Cargo.toml
index af772d64e..0c34b84a5 100644
--- a/packages/std/Cargo.toml
+++ b/packages/std/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "cosmwasm-std"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 authors = ["Ethan Frey <ethanfrey@users.noreply.github.com>"]
 edition = "2021"
 description = "Standard library for Wasm based smart contracts on Cosmos blockchains"
@@ -45,7 +45,7 @@ cosmwasm_1_3 = ["cosmwasm_1_2"]
 
 [dependencies]
 base64 = "0.13.0"
-cosmwasm-derive = { path = "../derive", version = "1.2.7" }
+cosmwasm-derive = { path = "../derive", version = "1.3.0-rc.0" }
 derivative = "2"
 forward_ref = "1"
 hex = "0.4"
@@ -54,10 +54,10 @@ sha2 = "0.10.3"
 serde = { version = "1.0.103", default-features = false, features = ["derive", "alloc"] }
 serde-json-wasm = { version = "0.5.0" }
 thiserror = "1.0.26"
-uint = "0.9.3"
+bnum = "0.7.0"
 
 [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
-cosmwasm-crypto = { path = "../crypto", version = "1.2.7" }
+cosmwasm-crypto = { path = "../crypto", version = "1.3.0-rc.0" }
 
 [dev-dependencies]
 cosmwasm-schema = { path = "../schema" }
diff --git a/packages/std/src/coins.rs b/packages/std/src/coins.rs
new file mode 100644
index 000000000..1548cfffa
--- /dev/null
+++ b/packages/std/src/coins.rs
@@ -0,0 +1,392 @@
+use std::collections::BTreeMap;
+use std::fmt;
+use std::str::FromStr;
+
+use crate::{errors::CoinsError, Coin, StdError, StdResult, Uint128};
+use crate::{OverflowError, OverflowOperation};
+
+/// A collection of coins, similar to Cosmos SDK's `sdk.Coins` struct.
+///
+/// Differently from `sdk.Coins`, which is a vector of `sdk.Coin`, here we
+/// implement Coins as a BTreeMap that maps from coin denoms to amounts.
+/// This has a number of advantages:
+///
+/// - coins are naturally sorted alphabetically by denom
+/// - duplicate denoms are automatically removed
+/// - cheaper for searching/inserting/deleting: O(log(n)) compared to O(n)
+#[derive(Clone, Default, Debug, PartialEq, Eq)]
+pub struct Coins(BTreeMap<String, Uint128>);
+
+/// Casting a Vec<Coin> to Coins.
+/// The Vec can be out of order, but must not contain duplicate denoms.
+/// If you want to sum up duplicates, create an empty instance using `Coins::default` and
+/// use `Coins::add` to add your coins.
+impl TryFrom<Vec<Coin>> for Coins {
+    type Error = CoinsError;
+
+    fn try_from(vec: Vec<Coin>) -> Result<Self, CoinsError> {
+        let mut map = BTreeMap::new();
+        for Coin { amount, denom } in vec {
+            if amount.is_zero() {
+                continue;
+            }
+
+            // if the insertion returns a previous value, we have a duplicate denom
+            if map.insert(denom, amount).is_some() {
+                return Err(CoinsError::DuplicateDenom);
+            }
+        }
+
+        Ok(Self(map))
+    }
+}
+
+impl TryFrom<&[Coin]> for Coins {
+    type Error = CoinsError;
+
+    fn try_from(slice: &[Coin]) -> Result<Self, CoinsError> {
+        slice.to_vec().try_into()
+    }
+}
+
+impl From<Coin> for Coins {
+    fn from(value: Coin) -> Self {
+        let mut coins = Coins::default();
+        // this can never overflow (because there are no coins in there yet), so we can unwrap
+        coins.add(value).unwrap();
+        coins
+    }
+}
+
+impl<const N: usize> TryFrom<[Coin; N]> for Coins {
+    type Error = CoinsError;
+
+    fn try_from(slice: [Coin; N]) -> Result<Self, CoinsError> {
+        slice.to_vec().try_into()
+    }
+}
+
+impl From<Coins> for Vec<Coin> {
+    fn from(value: Coins) -> Self {
+        value.into_vec()
+    }
+}
+
+impl FromStr for Coins {
+    type Err = StdError;
+
+    fn from_str(s: &str) -> StdResult<Self> {
+        if s.is_empty() {
+            return Ok(Self::default());
+        }
+
+        Ok(s.split(',')
+            .map(Coin::from_str)
+            .collect::<Result<Vec<_>, _>>()?
+            .try_into()?)
+    }
+}
+
+impl fmt::Display for Coins {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        let s = self
+            .0
+            .iter()
+            .map(|(denom, amount)| format!("{amount}{denom}"))
+            .collect::<Vec<_>>()
+            .join(",");
+        write!(f, "{s}")
+    }
+}
+
+impl Coins {
+    /// Conversion to Vec<Coin>, while NOT consuming the original object
+    pub fn to_vec(&self) -> Vec<Coin> {
+        self.0
+            .iter()
+            .map(|(denom, amount)| Coin {
+                denom: denom.clone(),
+                amount: *amount,
+            })
+            .collect()
+    }
+
+    /// Conversion to Vec<Coin>, consuming the original object
+    pub fn into_vec(self) -> Vec<Coin> {
+        self.0
+            .into_iter()
+            .map(|(denom, amount)| Coin { denom, amount })
+            .collect()
+    }
+
+    /// Returns the number of different denoms in this collection.
+    pub fn len(&self) -> usize {
+        self.0.len()
+    }
+
+    /// Returns `true` if this collection contains no coins.
+    pub fn is_empty(&self) -> bool {
+        self.0.is_empty()
+    }
+
+    /// Returns the denoms as a vector of strings.
+    /// The vector is guaranteed to not contain duplicates and sorted alphabetically.
+    pub fn denoms(&self) -> Vec<String> {
+        self.0.keys().cloned().collect()
+    }
+
+    /// Returns the amount of the given denom or zero if the denom is not present.
+    pub fn amount_of(&self, denom: &str) -> Uint128 {
+        self.0.get(denom).copied().unwrap_or_else(Uint128::zero)
+    }
+
+    /// Returns the amount of the given denom if and only if this collection contains only
+    /// the given denom. Otherwise `None` is returned.
+    ///
+    /// # Examples
+    ///
+    /// ```rust
+    /// use cosmwasm_std::{Coin, Coins, coin};
+    ///
+    /// let coins: Coins = [coin(100, "uatom")].try_into().unwrap();
+    /// assert_eq!(coins.contains_only("uatom").unwrap().u128(), 100);
+    /// assert_eq!(coins.contains_only("uluna"), None);
+    /// ```
+    ///
+    /// ```rust
+    /// use cosmwasm_std::{Coin, Coins, coin};
+    ///
+    /// let coins: Coins = [coin(100, "uatom"), coin(200, "uusd")].try_into().unwrap();
+    /// assert_eq!(coins.contains_only("uatom"), None);
+    /// ```
+    pub fn contains_only(&self, denom: &str) -> Option<Uint128> {
+        if self.len() == 1 {
+            self.0.get(denom).copied()
+        } else {
+            None
+        }
+    }
+
+    /// Adds the given coin to this `Coins` instance.
+    /// Errors in case of overflow.
+    pub fn add(&mut self, coin: Coin) -> StdResult<()> {
+        if coin.amount.is_zero() {
+            return Ok(());
+        }
+
+        let amount = self.0.entry(coin.denom).or_insert_with(Uint128::zero);
+        *amount = amount.checked_add(coin.amount)?;
+        Ok(())
+    }
+
+    /// Subtracts the given coin from this `Coins` instance.
+    /// Errors in case of overflow or if the denom is not present.
+    pub fn sub(&mut self, coin: Coin) -> StdResult<()> {
+        match self.0.get_mut(&coin.denom) {
+            Some(v) => {
+                *v = v.checked_sub(coin.amount)?;
+                // make sure to remove zero coin
+                if v.is_zero() {
+                    self.0.remove(&coin.denom);
+                }
+            }
+            None => {
+                // ignore zero subtraction
+                if coin.amount.is_zero() {
+                    return Ok(());
+                }
+                return Err(OverflowError::new(
+                    OverflowOperation::Sub,
+                    Uint128::zero(),
+                    coin.amount,
+                )
+                .into());
+            }
+        }
+
+        Ok(())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::coin;
+
+    /// Sort a Vec<Coin> by denom alphabetically
+    fn sort_by_denom(vec: &mut [Coin]) {
+        vec.sort_by(|a, b| a.denom.cmp(&b.denom));
+    }
+
+    /// Returns a mockup Vec<Coin>. In this example, the coins are not in order
+    fn mock_vec() -> Vec<Coin> {
+        vec![
+            coin(12345, "uatom"),
+            coin(69420, "ibc/1234ABCD"),
+            coin(88888, "factory/osmo1234abcd/subdenom"),
+        ]
+    }
+
+    /// Return a mockup Coins that contains the same coins as in `mock_vec`
+    fn mock_coins() -> Coins {
+        let mut coins = Coins::default();
+        for coin in mock_vec() {
+            coins.add(coin).unwrap();
+        }
+        coins
+    }
+
+    #[test]
+    fn converting_vec() {
+        let mut vec = mock_vec();
+        let coins = mock_coins();
+
+        // &[Coin] --> Coins
+        assert_eq!(Coins::try_from(vec.as_slice()).unwrap(), coins);
+        // Vec<Coin> --> Coins
+        assert_eq!(Coins::try_from(vec.clone()).unwrap(), coins);
+
+        sort_by_denom(&mut vec);
+
+        // &Coins --> Vec<Coins>
+        // NOTE: the returned vec should be sorted
+        assert_eq!(coins.to_vec(), vec);
+        // Coins --> Vec<Coins>
+        // NOTE: the returned vec should be sorted
+        assert_eq!(coins.into_vec(), vec);
+    }
+
+    #[test]
+    fn converting_str() {
+        // not in order
+        let s1 = "88888factory/osmo1234abcd/subdenom,12345uatom,69420ibc/1234ABCD";
+        // in order
+        let s2 = "88888factory/osmo1234abcd/subdenom,69420ibc/1234ABCD,12345uatom";
+
+        let invalid = "12345uatom,noamount";
+
+        let coins = mock_coins();
+
+        // &str --> Coins
+        // NOTE: should generate the same Coins, regardless of input order
+        assert_eq!(Coins::from_str(s1).unwrap(), coins);
+        assert_eq!(Coins::from_str(s2).unwrap(), coins);
+        assert_eq!(Coins::from_str("").unwrap(), Coins::default());
+
+        // Coins --> String
+        // NOTE: the generated string should be sorted
+        assert_eq!(coins.to_string(), s2);
+        assert_eq!(Coins::default().to_string(), "");
+        assert_eq!(
+            Coins::from_str(invalid).unwrap_err().to_string(),
+            "Generic error: Parsing Coin: Missing amount or non-digit characters in amount"
+        );
+    }
+
+    #[test]
+    fn handling_duplicates() {
+        // create a Vec<Coin> that contains duplicate denoms
+        let mut vec = mock_vec();
+        vec.push(coin(67890, "uatom"));
+
+        let err = Coins::try_from(vec).unwrap_err();
+        assert_eq!(err, CoinsError::DuplicateDenom);
+    }
+
+    #[test]
+    fn handling_zero_amount() {
+        // create a Vec<Coin> that contains zero amounts
+        let mut vec = mock_vec();
+        vec[0].amount = Uint128::zero();
+
+        let coins = Coins::try_from(vec).unwrap();
+        assert_eq!(coins.len(), 2);
+        assert_ne!(coins.amount_of("ibc/1234ABCD"), Uint128::zero());
+        assert_ne!(
+            coins.amount_of("factory/osmo1234abcd/subdenom"),
+            Uint128::zero()
+        );
+
+        // adding a coin with zero amount should not be added
+        let mut coins = Coins::default();
+        coins.add(coin(0, "uusd")).unwrap();
+        assert!(coins.is_empty());
+    }
+
+    #[test]
+    fn length() {
+        let coins = Coins::default();
+        assert_eq!(coins.len(), 0);
+        assert!(coins.is_empty());
+
+        let coins = mock_coins();
+        assert_eq!(coins.len(), 3);
+        assert!(!coins.is_empty());
+    }
+
+    #[test]
+    fn add_coin() {
+        let mut coins = mock_coins();
+
+        // existing denom
+        coins.add(coin(12345, "uatom")).unwrap();
+        assert_eq!(coins.len(), 3);
+        assert_eq!(coins.amount_of("uatom").u128(), 24690);
+
+        // new denom
+        coins.add(coin(123, "uusd")).unwrap();
+        assert_eq!(coins.len(), 4);
+
+        // zero amount
+        coins.add(coin(0, "uusd")).unwrap();
+        assert_eq!(coins.amount_of("uusd").u128(), 123);
+
+        // zero amount, new denom
+        coins.add(coin(0, "utest")).unwrap();
+        assert_eq!(coins.len(), 4);
+    }
+
+    #[test]
+    fn sub_coins() {
+        let mut coins: Coins = coin(12345, "uatom").into();
+
+        // sub more than available
+        let err = coins.sub(coin(12346, "uatom")).unwrap_err();
+        assert!(matches!(err, StdError::Overflow { .. }));
+
+        // sub non-existent denom
+        let err = coins.sub(coin(12345, "uusd")).unwrap_err();
+        assert!(matches!(err, StdError::Overflow { .. }));
+
+        // partial sub
+        coins.sub(coin(1, "uatom")).unwrap();
+        assert_eq!(coins.len(), 1);
+        assert_eq!(coins.amount_of("uatom").u128(), 12344);
+
+        // full sub
+        coins.sub(coin(12344, "uatom")).unwrap();
+        assert!(coins.is_empty());
+
+        // sub zero, existing denom
+        coins.sub(coin(0, "uusd")).unwrap();
+        assert!(coins.is_empty());
+        let mut coins: Coins = coin(12345, "uatom").into();
+
+        // sub zero, non-existent denom
+        coins.sub(coin(0, "uatom")).unwrap();
+        assert_eq!(coins.len(), 1);
+        assert_eq!(coins.amount_of("uatom").u128(), 12345);
+    }
+
+    #[test]
+    fn coin_to_coins() {
+        // zero coin results in empty collection
+        let coins: Coins = coin(0, "uusd").into();
+        assert!(coins.is_empty());
+
+        // happy path
+        let coins = Coins::from(coin(12345, "uatom"));
+        assert_eq!(coins.len(), 1);
+        assert_eq!(coins.amount_of("uatom").u128(), 12345);
+    }
+}
diff --git a/packages/std/src/errors/mod.rs b/packages/std/src/errors/mod.rs
index 5535479bd..54422007d 100644
--- a/packages/std/src/errors/mod.rs
+++ b/packages/std/src/errors/mod.rs
@@ -6,8 +6,8 @@ mod verification_error;
 pub use recover_pubkey_error::RecoverPubkeyError;
 pub use std_error::{
     CheckedFromRatioError, CheckedMultiplyFractionError, CheckedMultiplyRatioError,
-    CoinFromStrError, ConversionOverflowError, DivideByZeroError, OverflowError, OverflowOperation,
-    RoundUpOverflowError, StdError, StdResult,
+    CoinFromStrError, CoinsError, ConversionOverflowError, DivideByZeroError, DivisionError,
+    OverflowError, OverflowOperation, RoundUpOverflowError, StdError, StdResult,
 };
 pub use system_error::SystemError;
 pub use verification_error::VerificationError;
diff --git a/packages/std/src/errors/std_error.rs b/packages/std/src/errors/std_error.rs
index e77d0c0f1..8e2a486cd 100644
--- a/packages/std/src/errors/std_error.rs
+++ b/packages/std/src/errors/std_error.rs
@@ -556,6 +556,15 @@ impl DivideByZeroError {
     }
 }
 
+#[derive(Error, Debug, PartialEq, Eq)]
+pub enum DivisionError {
+    #[error("Divide by zero")]
+    DivideByZero,
+
+    #[error("Overflow in division")]
+    Overflow,
+}
+
 #[derive(Error, Debug, PartialEq, Eq)]
 pub enum CheckedMultiplyFractionError {
     #[error("{0}")]
@@ -590,6 +599,18 @@ pub enum CheckedFromRatioError {
 #[error("Round up operation failed because of overflow")]
 pub struct RoundUpOverflowError;
 
+#[derive(Error, Debug, PartialEq, Eq)]
+pub enum CoinsError {
+    #[error("Duplicate denom")]
+    DuplicateDenom,
+}
+
+impl From<CoinsError> for StdError {
+    fn from(value: CoinsError) -> Self {
+        Self::generic_err(format!("Creating Coins: {value}"))
+    }
+}
+
 #[derive(Error, Debug, PartialEq, Eq)]
 pub enum CoinFromStrError {
     #[error("Missing denominator")]
diff --git a/packages/std/src/lib.rs b/packages/std/src/lib.rs
index d2e156883..0f26389b3 100644
--- a/packages/std/src/lib.rs
+++ b/packages/std/src/lib.rs
@@ -7,6 +7,7 @@ mod addresses;
 mod assertions;
 mod binary;
 mod coin;
+mod coins;
 mod conversion;
 mod deps;
 mod errors;
@@ -38,6 +39,7 @@ pub mod storage_keys;
 pub use crate::addresses::{instantiate2_address, Addr, CanonicalAddr, Instantiate2AddressError};
 pub use crate::binary::Binary;
 pub use crate::coin::{coin, coins, has_coins, Coin};
+pub use crate::coins::Coins;
 pub use crate::deps::{Deps, DepsMut, OwnedDeps};
 pub use crate::errors::{
     CheckedFromRatioError, CheckedMultiplyFractionError, CheckedMultiplyRatioError,
@@ -55,8 +57,8 @@ pub use crate::ibc::{
 #[cfg(feature = "iterator")]
 pub use crate::iterator::{Order, Record};
 pub use crate::math::{
-    Decimal, Decimal256, Decimal256RangeExceeded, DecimalRangeExceeded, Fraction, Isqrt, Uint128,
-    Uint256, Uint512, Uint64,
+    Decimal, Decimal256, Decimal256RangeExceeded, DecimalRangeExceeded, Fraction, Int128, Int256,
+    Int512, Int64, Isqrt, Uint128, Uint256, Uint512, Uint64,
 };
 pub use crate::metadata::{DenomMetadata, DenomUnit};
 pub use crate::never::Never;
diff --git a/packages/std/src/math/decimal256.rs b/packages/std/src/math/decimal256.rs
index b3df870ad..2b98a82ce 100644
--- a/packages/std/src/math/decimal256.rs
+++ b/packages/std/src/math/decimal256.rs
@@ -2070,7 +2070,7 @@ mod tests {
     }
 
     #[test]
-    #[should_panic(expected = "division by zero")]
+    #[should_panic(expected = "divisor of zero")]
     fn decimal256_rem_panics_for_zero() {
         let _ = Decimal256::percent(777) % Decimal256::zero();
     }
diff --git a/packages/std/src/math/int128.rs b/packages/std/src/math/int128.rs
new file mode 100644
index 000000000..0a44bd274
--- /dev/null
+++ b/packages/std/src/math/int128.rs
@@ -0,0 +1,1098 @@
+use forward_ref::{forward_ref_binop, forward_ref_op_assign};
+use schemars::JsonSchema;
+use serde::{de, ser, Deserialize, Deserializer, Serialize};
+use std::fmt;
+use std::ops::{
+    Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Not, Rem, RemAssign, Shl, ShlAssign, Shr,
+    ShrAssign, Sub, SubAssign,
+};
+use std::str::FromStr;
+
+use crate::errors::{DivideByZeroError, DivisionError, OverflowError, OverflowOperation, StdError};
+use crate::{forward_ref_partial_eq, Int64, Uint128, Uint64};
+
+/// An implementation of i128 that is using strings for JSON encoding/decoding,
+/// such that the full i128 range can be used for clients that convert JSON numbers to floats,
+/// like JavaScript and jq.
+///
+/// # Examples
+///
+/// Use `from` to create instances of this and `i128` to get the value out:
+///
+/// ```
+/// # use cosmwasm_std::Int128;
+/// let a = Int128::from(258i128);
+/// assert_eq!(a.i128(), 258);
+/// ```
+#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, JsonSchema)]
+pub struct Int128(#[schemars(with = "String")] i128);
+
+forward_ref_partial_eq!(Int128, Int128);
+
+impl Int128 {
+    pub const MAX: Int128 = Int128(i128::MAX);
+    pub const MIN: Int128 = Int128(i128::MIN);
+
+    /// Creates a Int128(value).
+    ///
+    /// This method is less flexible than `from` but can be called in a const context.
+    #[inline]
+    pub const fn new(value: i128) -> Self {
+        Self(value)
+    }
+
+    /// Creates a Int128(0)
+    #[inline]
+    pub const fn zero() -> Self {
+        Int128(0)
+    }
+
+    /// Creates a Int128(1)
+    #[inline]
+    pub const fn one() -> Self {
+        Self(1)
+    }
+
+    /// Returns a copy of the internal data
+    pub const fn i128(&self) -> i128 {
+        self.0
+    }
+
+    #[must_use]
+    pub const fn from_be_bytes(data: [u8; 16]) -> Self {
+        Self(i128::from_be_bytes(data))
+    }
+
+    #[must_use]
+    pub const fn from_le_bytes(data: [u8; 16]) -> Self {
+        Self(i128::from_le_bytes(data))
+    }
+
+    /// Returns a copy of the number as big endian bytes.
+    #[must_use = "this returns the result of the operation, without modifying the original"]
+    pub const fn to_be_bytes(self) -> [u8; 16] {
+        self.0.to_be_bytes()
+    }
+
+    /// Returns a copy of the number as little endian bytes.
+    #[must_use = "this returns the result of the operation, without modifying the original"]
+    pub const fn to_le_bytes(self) -> [u8; 16] {
+        self.0.to_le_bytes()
+    }
+
+    #[must_use]
+    pub const fn is_zero(&self) -> bool {
+        self.0 == 0
+    }
+
+    #[must_use = "this returns the result of the operation, without modifying the original"]
+    pub fn pow(self, exp: u32) -> Self {
+        Self(self.0.pow(exp))
+    }
+
+    pub fn checked_add(self, other: Self) -> Result<Self, OverflowError> {
+        self.0
+            .checked_add(other.0)
+            .map(Self)
+            .ok_or_else(|| OverflowError::new(OverflowOperation::Add, self, other))
+    }
+
+    pub fn checked_sub(self, other: Self) -> Result<Self, OverflowError> {
+        self.0
+            .checked_sub(other.0)
+            .map(Self)
+            .ok_or_else(|| OverflowError::new(OverflowOperation::Sub, self, other))
+    }
+
+    pub fn checked_mul(self, other: Self) -> Result<Self, OverflowError> {
+        self.0
+            .checked_mul(other.0)
+            .map(Self)
+            .ok_or_else(|| OverflowError::new(OverflowOperation::Mul, self, other))
+    }
+
+    pub fn checked_pow(self, exp: u32) -> Result<Self, OverflowError> {
+        self.0
+            .checked_pow(exp)
+            .map(Self)
+            .ok_or_else(|| OverflowError::new(OverflowOperation::Pow, self, exp))
+    }
+
+    pub fn checked_div(self, other: Self) -> Result<Self, DivisionError> {
+        if other.is_zero() {
+            return Err(DivisionError::DivideByZero);
+        }
+        self.0
+            .checked_div(other.0)
+            .map(Self)
+            .ok_or(DivisionError::Overflow)
+    }
+
+    pub fn checked_div_euclid(self, other: Self) -> Result<Self, DivisionError> {
+        if other.is_zero() {
+            return Err(DivisionError::DivideByZero);
+        }
+        self.0
+            .checked_div_euclid(other.0)
+            .map(Self)
+            .ok_or(DivisionError::Overflow)
+    }
+
+    pub fn checked_rem(self, other: Self) -> Result<Self, DivideByZeroError> {
+        self.0
+            .checked_rem(other.0)
+            .map(Self)
+            .ok_or_else(|| DivideByZeroError::new(self))
+    }
+
+    pub fn checked_shr(self, other: u32) -> Result<Self, OverflowError> {
+        if other >= 128 {
+            return Err(OverflowError::new(OverflowOperation::Shr, self, other));
+        }
+
+        Ok(Self(self.0.shr(other)))
+    }
+
+    pub fn checked_shl(self, other: u32) -> Result<Self, OverflowError> {
+        if other >= 128 {
+            return Err(OverflowError::new(OverflowOperation::Shl, self, other));
+        }
+
+        Ok(Self(self.0.shl(other)))
+    }
+
+    #[must_use = "this returns the result of the operation, without modifying the original"]
+    #[inline]
+    pub fn wrapping_add(self, other: Self) -> Self {
+        Self(self.0.wrapping_add(other.0))
+    }
+
+    #[must_use = "this returns the result of the operation, without modifying the original"]
+    #[inline]
+    pub fn wrapping_sub(self, other: Self) -> Self {
+        Self(self.0.wrapping_sub(other.0))
+    }
+
+    #[must_use = "this returns the result of the operation, without modifying the original"]
+    #[inline]
+    pub fn wrapping_mul(self, other: Self) -> Self {
+        Self(self.0.wrapping_mul(other.0))
+    }
+
+    #[must_use = "this returns the result of the operation, without modifying the original"]
+    #[inline]
+    pub fn wrapping_pow(self, other: u32) -> Self {
+        Self(self.0.wrapping_pow(other))
+    }
+
+    #[must_use = "this returns the result of the operation, without modifying the original"]
+    pub fn saturating_add(self, other: Self) -> Self {
+        Self(self.0.saturating_add(other.0))
+    }
+
+    #[must_use = "this returns the result of the operation, without modifying the original"]
+    pub fn saturating_sub(self, other: Self) -> Self {
+        Self(self.0.saturating_sub(other.0))
+    }
+
+    #[must_use = "this returns the result of the operation, without modifying the original"]
+    pub fn saturating_mul(self, other: Self) -> Self {
+        Self(self.0.saturating_mul(other.0))
+    }
+
+    #[must_use = "this returns the result of the operation, without modifying the original"]
+    pub fn saturating_pow(self, exp: u32) -> Self {
+        Self(self.0.saturating_pow(exp))
+    }
+
+    #[must_use = "this returns the result of the operation, without modifying the original"]
+    pub fn abs_diff(self, other: Self) -> Uint128 {
+        Uint128(self.0.abs_diff(other.0))
+    }
+}
+
+impl From<Uint64> for Int128 {
+    fn from(val: Uint64) -> Self {
+        val.u64().into()
+    }
+}
+
+impl From<u64> for Int128 {
+    fn from(val: u64) -> Self {
+        Int128(val.into())
+    }
+}
+
+impl From<u32> for Int128 {
+    fn from(val: u32) -> Self {
+        Int128(val.into())
+    }
+}
+
+impl From<u16> for Int128 {
+    fn from(val: u16) -> Self {
+        Int128(val.into())
+    }
+}
+
+impl From<u8> for Int128 {
+    fn from(val: u8) -> Self {
+        Int128(val.into())
+    }
+}
+
+impl From<Int64> for Int128 {
+    fn from(val: Int64) -> Self {
+        val.i64().into()
+    }
+}
+
+impl From<i128> for Int128 {
+    fn from(val: i128) -> Self {
+        Int128(val)
+    }
+}
+
+impl From<i64> for Int128 {
+    fn from(val: i64) -> Self {
+        Int128(val.into())
+    }
+}
+
+impl From<i32> for Int128 {
+    fn from(val: i32) -> Self {
+        Int128(val.into())
+    }
+}
+
+impl From<i16> for Int128 {
+    fn from(val: i16) -> Self {
+        Int128(val.into())
+    }
+}
+
+impl From<i8> for Int128 {
+    fn from(val: i8) -> Self {
+        Int128(val.into())
+    }
+}
+
+impl TryFrom<&str> for Int128 {
+    type Error = StdError;
+
+    fn try_from(val: &str) -> Result<Self, Self::Error> {
+        Self::from_str(val)
+    }
+}
+
+impl FromStr for Int128 {
+    type Err = StdError;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        match s.parse::<i128>() {
+            Ok(u) => Ok(Self(u)),
+            Err(e) => Err(StdError::generic_err(format!("Parsing Int128: {e}"))),
+        }
+    }
+}
+
+impl From<Int128> for String {
+    fn from(original: Int128) -> Self {
+        original.to_string()
+    }
+}
+
+impl fmt::Display for Int128 {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        self.0.fmt(f)
+    }
+}
+
+impl Add<Int128> for Int128 {
+    type Output = Self;
+
+    fn add(self, rhs: Self) -> Self {
+        Int128(self.0.checked_add(rhs.0).unwrap())
+    }
+}
+forward_ref_binop!(impl Add, add for Int128, Int128);
+
+impl Sub<Int128> for Int128 {
+    type Output = Self;
+
+    fn sub(self, rhs: Self) -> Self {
+        Int128(self.0.checked_sub(rhs.0).unwrap())
+    }
+}
+forward_ref_binop!(impl Sub, sub for Int128, Int128);
+
+impl SubAssign<Int128> for Int128 {
+    fn sub_assign(&mut self, rhs: Int128) {
+        self.0 = self.0.checked_sub(rhs.0).unwrap();
+    }
+}
+forward_ref_op_assign!(impl SubAssign, sub_assign for Int128, Int128);
+
+impl Div<Int128> for Int128 {
+    type Output = Self;
+
+    fn div(self, rhs: Self) -> Self::Output {
+        Self(self.0.checked_div(rhs.0).unwrap())
+    }
+}
+forward_ref_binop!(impl Div, div for Int128, Int128);
+
+impl Rem for Int128 {
+    type Output = Self;
+
+    /// # Panics
+    ///
+    /// This operation will panic if `rhs` is zero.
+    #[inline]
+    fn rem(self, rhs: Self) -> Self {
+        Self(self.0.rem(rhs.0))
+    }
+}
+forward_ref_binop!(impl Rem, rem for Int128, Int128);
+
+impl Not for Int128 {
+    type Output = Self;
+
+    fn not(self) -> Self::Output {
+        Self(!self.0)
+    }
+}
+
+impl Neg for Int128 {
+    type Output = Self;
+
+    fn neg(self) -> Self::Output {
+        Self(-self.0)
+    }
+}
+
+impl RemAssign<Int128> for Int128 {
+    fn rem_assign(&mut self, rhs: Int128) {
+        *self = *self % rhs;
+    }
+}
+forward_ref_op_assign!(impl RemAssign, rem_assign for Int128, Int128);
+
+impl Mul<Int128> for Int128 {
+    type Output = Self;
+
+    fn mul(self, rhs: Self) -> Self::Output {
+        Self(self.0.checked_mul(rhs.0).unwrap())
+    }
+}
+forward_ref_binop!(impl Mul, mul for Int128, Int128);
+
+impl MulAssign<Int128> for Int128 {
+    fn mul_assign(&mut self, rhs: Self) {
+        self.0 = self.0.checked_mul(rhs.0).unwrap();
+    }
+}
+forward_ref_op_assign!(impl MulAssign, mul_assign for Int128, Int128);
+
+impl Shr<u32> for Int128 {
+    type Output = Self;
+
+    fn shr(self, rhs: u32) -> Self::Output {
+        self.checked_shr(rhs).unwrap_or_else(|_| {
+            panic!("right shift error: {rhs} is larger or equal than the number of bits in Int128",)
+        })
+    }
+}
+forward_ref_binop!(impl Shr, shr for Int128, u32);
+
+impl Shl<u32> for Int128 {
+    type Output = Self;
+
+    fn shl(self, rhs: u32) -> Self::Output {
+        self.checked_shl(rhs).unwrap_or_else(|_| {
+            panic!("left shift error: {rhs} is larger or equal than the number of bits in Int128",)
+        })
+    }
+}
+forward_ref_binop!(impl Shl, shl for Int128, u32);
+
+impl AddAssign<Int128> for Int128 {
+    fn add_assign(&mut self, rhs: Int128) {
+        self.0 = self.0.checked_add(rhs.0).unwrap();
+    }
+}
+forward_ref_op_assign!(impl AddAssign, add_assign for Int128, Int128);
+
+impl DivAssign<Int128> for Int128 {
+    fn div_assign(&mut self, rhs: Self) {
+        self.0 = self.0.checked_div(rhs.0).unwrap();
+    }
+}
+forward_ref_op_assign!(impl DivAssign, div_assign for Int128, Int128);
+
+impl ShrAssign<u32> for Int128 {
+    fn shr_assign(&mut self, rhs: u32) {
+        *self = Shr::<u32>::shr(*self, rhs);
+    }
+}
+forward_ref_op_assign!(impl ShrAssign, shr_assign for Int128, u32);
+
+impl ShlAssign<u32> for Int128 {
+    fn shl_assign(&mut self, rhs: u32) {
+        *self = Shl::<u32>::shl(*self, rhs);
+    }
+}
+forward_ref_op_assign!(impl ShlAssign, shl_assign for Int128, u32);
+
+impl Serialize for Int128 {
+    /// Serializes as an integer string using base 10
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: ser::Serializer,
+    {
+        serializer.serialize_str(&self.to_string())
+    }
+}
+
+impl<'de> Deserialize<'de> for Int128 {
+    /// Deserialized from an integer string using base 10
+    fn deserialize<D>(deserializer: D) -> Result<Int128, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        deserializer.deserialize_str(Int128Visitor)
+    }
+}
+
+struct Int128Visitor;
+
+impl<'de> de::Visitor<'de> for Int128Visitor {
+    type Value = Int128;
+
+    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+        formatter.write_str("string-encoded integer")
+    }
+
+    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
+    where
+        E: de::Error,
+    {
+        Int128::try_from(v).map_err(|e| E::custom(format!("invalid Int128 '{v}' - {e}")))
+    }
+}
+
+impl<A> std::iter::Sum<A> for Int128
+where
+    Self: Add<A, Output = Self>,
+{
+    fn sum<I: Iterator<Item = A>>(iter: I) -> Self {
+        iter.fold(Self::zero(), Add::add)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::{from_slice, to_vec};
+
+    #[test]
+    fn size_of_works() {
+        assert_eq!(std::mem::size_of::<Int128>(), 16);
+    }
+
+    #[test]
+    fn int128_from_be_bytes_works() {
+        let num = Int128::from_be_bytes([1; 16]);
+        let a: [u8; 16] = num.to_be_bytes();
+        assert_eq!(a, [1; 16]);
+
+        let be_bytes = [
+            0u8, 222u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 1u8, 2u8, 3u8,
+        ];
+        let num = Int128::from_be_bytes(be_bytes);
+        let resulting_bytes: [u8; 16] = num.to_be_bytes();
+        assert_eq!(be_bytes, resulting_bytes);
+    }
+
+    #[test]
+    fn int128_new_works() {
+        let num = Int128::new(222);
+        assert_eq!(num.i128(), 222);
+
+        let num = Int128::new(-222);
+        assert_eq!(num.i128(), -222);
+
+        let num = Int128::new(i128::MAX);
+        assert_eq!(num.i128(), i128::MAX);
+
+        let num = Int128::new(i128::MIN);
+        assert_eq!(num.i128(), i128::MIN);
+    }
+
+    #[test]
+    fn int128_zero_works() {
+        let zero = Int128::zero();
+        assert_eq!(zero.to_be_bytes(), [0; 16]);
+    }
+
+    #[test]
+    fn uint128_one_works() {
+        let one = Int128::one();
+        let mut one_be = [0; 16];
+        one_be[15] = 1;
+
+        assert_eq!(one.to_be_bytes(), one_be);
+    }
+
+    #[test]
+    fn int128_endianness() {
+        let be_bytes = [
+            0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 1u8, 2u8, 3u8,
+        ];
+        let le_bytes = [
+            3u8, 2u8, 1u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8,
+        ];
+
+        // These should all be the same.
+        let num1 = Int128::from_be_bytes(be_bytes);
+        let num2 = Int128::from_le_bytes(le_bytes);
+        assert_eq!(num1, Int128::from(65536u32 + 512 + 3));
+        assert_eq!(num1, num2);
+    }
+
+    #[test]
+    fn int128_convert_from() {
+        let a = Int128::from(5i128);
+        assert_eq!(a.0, i128::from(5u32));
+
+        let a = Int128::from(5u64);
+        assert_eq!(a.0, i128::from(5u32));
+
+        let a = Int128::from(5u32);
+        assert_eq!(a.0, i128::from(5u32));
+
+        let a = Int128::from(5u16);
+        assert_eq!(a.0, i128::from(5u32));
+
+        let a = Int128::from(5u8);
+        assert_eq!(a.0, i128::from(5u32));
+
+        let a = Int128::from(-5i128);
+        assert_eq!(a.0, i128::from(-5i32));
+
+        let a = Int128::from(-5i64);
+        assert_eq!(a.0, i128::from(-5i32));
+
+        let a = Int128::from(-5i32);
+        assert_eq!(a.0, i128::from(-5i32));
+
+        let a = Int128::from(-5i16);
+        assert_eq!(a.0, i128::from(-5i32));
+
+        let a = Int128::from(-5i8);
+        assert_eq!(a.0, i128::from(-5i32));
+
+        let result = Int128::try_from("34567");
+        assert_eq!(result.unwrap().0, "34567".parse::<i128>().unwrap());
+
+        let result = Int128::try_from("1.23");
+        assert!(result.is_err());
+    }
+
+    #[test]
+    fn int128_implements_display() {
+        let a = Int128::from(12345u32);
+        assert_eq!(format!("Embedded: {a}"), "Embedded: 12345");
+        assert_eq!(a.to_string(), "12345");
+
+        let a = Int128::from(-12345i32);
+        assert_eq!(format!("Embedded: {a}"), "Embedded: -12345");
+        assert_eq!(a.to_string(), "-12345");
+
+        let a = Int128::zero();
+        assert_eq!(format!("Embedded: {a}"), "Embedded: 0");
+        assert_eq!(a.to_string(), "0");
+    }
+
+    #[test]
+    fn int128_display_padding_works() {
+        let a = Int128::from(123u64);
+        assert_eq!(format!("Embedded: {a:05}"), "Embedded: 00123");
+
+        let a = Int128::from(-123i64);
+        assert_eq!(format!("Embedded: {a:05}"), "Embedded: -0123");
+    }
+
+    #[test]
+    fn int128_to_be_bytes_works() {
+        assert_eq!(Int128::zero().to_be_bytes(), [0; 16]);
+
+        let mut max = [0xff; 16];
+        max[0] = 0x7f;
+        assert_eq!(Int128::MAX.to_be_bytes(), max);
+
+        let mut one = [0; 16];
+        one[15] = 1;
+        assert_eq!(Int128::from(1i128).to_be_bytes(), one);
+        // Python: `[b for b in (70141183460469231731687303715884018880).to_bytes(16, "big")]`
+        assert_eq!(
+            Int128::from(70141183460469231731687303715884018880i128).to_be_bytes(),
+            [52, 196, 179, 87, 165, 121, 59, 133, 246, 117, 221, 191, 255, 254, 172, 192]
+        );
+        assert_eq!(
+            Int128::from_be_bytes([17, 4, 23, 32, 87, 67, 123, 200, 58, 91, 0, 38, 33, 21, 67, 78])
+                .to_be_bytes(),
+            [17, 4, 23, 32, 87, 67, 123, 200, 58, 91, 0, 38, 33, 21, 67, 78]
+        );
+    }
+
+    #[test]
+    fn int128_to_le_bytes_works() {
+        assert_eq!(Int128::zero().to_le_bytes(), [0; 16]);
+
+        let mut max = [0xff; 16];
+        max[15] = 0x7f;
+        assert_eq!(Int128::MAX.to_le_bytes(), max);
+
+        let mut one = [0; 16];
+        one[0] = 1;
+        assert_eq!(Int128::from(1i128).to_le_bytes(), one);
+        // Python: `[b for b in (70141183460469231731687303715884018880).to_bytes(16, "little")]`
+        assert_eq!(
+            Int128::from(70141183460469231731687303715884018880i128).to_le_bytes(),
+            [192, 172, 254, 255, 191, 221, 117, 246, 133, 59, 121, 165, 87, 179, 196, 52]
+        );
+        assert_eq!(
+            Int128::from_be_bytes([17, 4, 23, 32, 87, 67, 123, 200, 58, 91, 0, 38, 33, 21, 67, 78])
+                .to_le_bytes(),
+            [78, 67, 21, 33, 38, 0, 91, 58, 200, 123, 67, 87, 32, 23, 4, 17]
+        );
+    }
+
+    #[test]
+    fn int128_is_zero_works() {
+        assert!(Int128::zero().is_zero());
+        assert!(Int128(i128::from(0u32)).is_zero());
+
+        assert!(!Int128::from(1u32).is_zero());
+        assert!(!Int128::from(123u32).is_zero());
+        assert!(!Int128::from(-123i32).is_zero());
+    }
+
+    #[test]
+    fn int128_wrapping_methods() {
+        // wrapping_add
+        assert_eq!(
+            Int128::from(2u32).wrapping_add(Int128::from(2u32)),
+            Int128::from(4u32)
+        ); // non-wrapping
+        assert_eq!(Int128::MAX.wrapping_add(Int128::from(1u32)), Int128::MIN); // wrapping
+
+        // wrapping_sub
+        assert_eq!(
+            Int128::from(7u32).wrapping_sub(Int128::from(5u32)),
+            Int128::from(2u32)
+        ); // non-wrapping
+        assert_eq!(Int128::MIN.wrapping_sub(Int128::from(1u32)), Int128::MAX); // wrapping
+
+        // wrapping_mul
+        assert_eq!(
+            Int128::from(3u32).wrapping_mul(Int128::from(2u32)),
+            Int128::from(6u32)
+        ); // non-wrapping
+        assert_eq!(
+            Int128::MAX.wrapping_mul(Int128::from(2u32)),
+            Int128::from(-2i32)
+        ); // wrapping
+
+        // wrapping_pow
+        assert_eq!(Int128::from(2u32).wrapping_pow(3), Int128::from(8u32)); // non-wrapping
+        assert_eq!(Int128::MAX.wrapping_pow(2), Int128::from(1u32)); // wrapping
+    }
+
+    #[test]
+    fn int128_json() {
+        let orig = Int128::from(1234567890987654321i128);
+        let serialized = to_vec(&orig).unwrap();
+        assert_eq!(serialized.as_slice(), b"\"1234567890987654321\"");
+        let parsed: Int128 = from_slice(&serialized).unwrap();
+        assert_eq!(parsed, orig);
+    }
+
+    #[test]
+    fn int128_compare() {
+        let a = Int128::from(12345u32);
+        let b = Int128::from(23456u32);
+
+        assert!(a < b);
+        assert!(b > a);
+        assert_eq!(a, Int128::from(12345u32));
+    }
+
+    #[test]
+    #[allow(clippy::op_ref)]
+    fn int128_math() {
+        let a = Int128::from(-12345i32);
+        let b = Int128::from(23456u32);
+
+        // test + with owned and reference right hand side
+        assert_eq!(a + b, Int128::from(11111u32));
+        assert_eq!(a + &b, Int128::from(11111u32));
+
+        // test - with owned and reference right hand side
+        assert_eq!(b - a, Int128::from(35801u32));
+        assert_eq!(b - &a, Int128::from(35801u32));
+
+        // test += with owned and reference right hand side
+        let mut c = Int128::from(300000u32);
+        c += b;
+        assert_eq!(c, Int128::from(323456u32));
+        let mut d = Int128::from(300000u32);
+        d += &b;
+        assert_eq!(d, Int128::from(323456u32));
+
+        // test -= with owned and reference right hand side
+        let mut c = Int128::from(300000u32);
+        c -= b;
+        assert_eq!(c, Int128::from(276544u32));
+        let mut d = Int128::from(300000u32);
+        d -= &b;
+        assert_eq!(d, Int128::from(276544u32));
+
+        // test - with negative result
+        assert_eq!(a - b, Int128::from(-35801i32));
+    }
+
+    #[test]
+    #[should_panic]
+    fn int128_add_overflow_panics() {
+        let _ = Int128::MAX + Int128::from(12u32);
+    }
+
+    #[test]
+    #[allow(clippy::op_ref)]
+    fn int128_sub_works() {
+        assert_eq!(Int128::from(2u32) - Int128::from(1u32), Int128::from(1u32));
+        assert_eq!(Int128::from(2u32) - Int128::from(0u32), Int128::from(2u32));
+        assert_eq!(Int128::from(2u32) - Int128::from(2u32), Int128::from(0u32));
+        assert_eq!(Int128::from(2u32) - Int128::from(3u32), Int128::from(-1i32));
+
+        // works for refs
+        let a = Int128::from(10u32);
+        let b = Int128::from(3u32);
+        let expected = Int128::from(7u32);
+        assert_eq!(a - b, expected);
+        assert_eq!(a - &b, expected);
+        assert_eq!(&a - b, expected);
+        assert_eq!(&a - &b, expected);
+    }
+
+    #[test]
+    #[should_panic]
+    fn int128_sub_overflow_panics() {
+        let _ = Int128::MIN + Int128::one() - Int128::from(2u32);
+    }
+
+    #[test]
+    fn int128_sub_assign_works() {
+        let mut a = Int128::from(14u32);
+        a -= Int128::from(2u32);
+        assert_eq!(a, Int128::from(12u32));
+
+        // works for refs
+        let mut a = Int128::from(10u32);
+        let b = Int128::from(3u32);
+        let expected = Int128::from(7u32);
+        a -= &b;
+        assert_eq!(a, expected);
+    }
+
+    #[test]
+    #[allow(clippy::op_ref)]
+    fn int128_mul_works() {
+        assert_eq!(Int128::from(2u32) * Int128::from(3u32), Int128::from(6u32));
+        assert_eq!(Int128::from(2u32) * Int128::zero(), Int128::zero());
+
+        // works for refs
+        let a = Int128::from(11u32);
+        let b = Int128::from(3u32);
+        let expected = Int128::from(33u32);
+        assert_eq!(a * b, expected);
+        assert_eq!(a * &b, expected);
+        assert_eq!(&a * b, expected);
+        assert_eq!(&a * &b, expected);
+    }
+
+    #[test]
+    fn int128_mul_assign_works() {
+        let mut a = Int128::from(14u32);
+        a *= Int128::from(2u32);
+        assert_eq!(a, Int128::from(28u32));
+
+        // works for refs
+        let mut a = Int128::from(10u32);
+        let b = Int128::from(3u32);
+        a *= &b;
+        assert_eq!(a, Int128::from(30u32));
+    }
+
+    #[test]
+    fn int128_pow_works() {
+        assert_eq!(Int128::from(2u32).pow(2), Int128::from(4u32));
+        assert_eq!(Int128::from(2u32).pow(10), Int128::from(1024u32));
+    }
+
+    #[test]
+    #[should_panic]
+    fn int128_pow_overflow_panics() {
+        _ = Int128::MAX.pow(2u32);
+    }
+
+    #[test]
+    fn int128_shr_works() {
+        let original = Int128::from_be_bytes([
+            0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 2u8, 0u8, 4u8, 2u8,
+        ]);
+
+        let shifted = Int128::from_be_bytes([
+            0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 128u8, 1u8, 0u8,
+        ]);
+
+        assert_eq!(original >> 2u32, shifted);
+    }
+
+    #[test]
+    #[should_panic]
+    fn int128_shr_overflow_panics() {
+        let _ = Int128::from(1u32) >> 128u32;
+    }
+
+    #[test]
+    fn sum_works() {
+        let nums = vec![
+            Int128::from(17u32),
+            Int128::from(123u32),
+            Int128::from(540u32),
+            Int128::from(82u32),
+        ];
+        let expected = Int128::from(762u32);
+
+        let sum_as_ref: Int128 = nums.iter().sum();
+        assert_eq!(expected, sum_as_ref);
+
+        let sum_as_owned: Int128 = nums.into_iter().sum();
+        assert_eq!(expected, sum_as_owned);
+    }
+
+    #[test]
+    fn int128_methods() {
+        // checked_*
+        assert!(matches!(
+            Int128::MAX.checked_add(Int128::from(1u32)),
+            Err(OverflowError { .. })
+        ));
+        assert_eq!(
+            Int128::from(1u32).checked_add(Int128::from(1u32)),
+            Ok(Int128::from(2u32)),
+        );
+        assert!(matches!(
+            Int128::MIN.checked_sub(Int128::from(1u32)),
+            Err(OverflowError { .. })
+        ));
+        assert_eq!(
+            Int128::from(2u32).checked_sub(Int128::from(1u32)),
+            Ok(Int128::from(1u32)),
+        );
+        assert!(matches!(
+            Int128::MAX.checked_mul(Int128::from(2u32)),
+            Err(OverflowError { .. })
+        ));
+        assert_eq!(
+            Int128::from(2u32).checked_mul(Int128::from(2u32)),
+            Ok(Int128::from(4u32)),
+        );
+        assert!(matches!(
+            Int128::MAX.checked_pow(2u32),
+            Err(OverflowError { .. })
+        ));
+        assert_eq!(Int128::from(2u32).checked_pow(3u32), Ok(Int128::from(8u32)),);
+        assert_eq!(
+            Int128::MAX.checked_div(Int128::from(0u32)),
+            Err(DivisionError::DivideByZero)
+        );
+        assert_eq!(
+            Int128::from(6u32).checked_div(Int128::from(2u32)),
+            Ok(Int128::from(3u32)),
+        );
+        assert_eq!(
+            Int128::MAX.checked_div_euclid(Int128::from(0u32)),
+            Err(DivisionError::DivideByZero)
+        );
+        assert_eq!(
+            Int128::from(6u32).checked_div_euclid(Int128::from(2u32)),
+            Ok(Int128::from(3u32)),
+        );
+        assert_eq!(
+            Int128::from(7u32).checked_div_euclid(Int128::from(2u32)),
+            Ok(Int128::from(3u32)),
+        );
+        assert!(matches!(
+            Int128::MAX.checked_rem(Int128::from(0u32)),
+            Err(DivideByZeroError { .. })
+        ));
+        // checked_* with negative numbers
+        assert_eq!(
+            Int128::from(-12i32).checked_div(Int128::from(10i32)),
+            Ok(Int128::from(-1i32)),
+        );
+        assert_eq!(
+            Int128::from(-2i32).checked_pow(3u32),
+            Ok(Int128::from(-8i32)),
+        );
+        assert_eq!(
+            Int128::from(-6i32).checked_mul(Int128::from(-7i32)),
+            Ok(Int128::from(42i32)),
+        );
+        assert_eq!(
+            Int128::from(-2i32).checked_add(Int128::from(3i32)),
+            Ok(Int128::from(1i32)),
+        );
+        assert_eq!(
+            Int128::from(-1i32).checked_div_euclid(Int128::from(-2i32)),
+            Ok(Int128::from(1u32)),
+        );
+
+        // saturating_*
+        assert_eq!(Int128::MAX.saturating_add(Int128::from(1u32)), Int128::MAX);
+        assert_eq!(Int128::MIN.saturating_sub(Int128::from(1u32)), Int128::MIN);
+        assert_eq!(Int128::MAX.saturating_mul(Int128::from(2u32)), Int128::MAX);
+        assert_eq!(Int128::from(4u32).saturating_pow(2u32), Int128::from(16u32));
+        assert_eq!(Int128::MAX.saturating_pow(2u32), Int128::MAX);
+    }
+
+    #[test]
+    #[allow(clippy::op_ref)]
+    fn int128_implements_rem() {
+        let a = Int128::from(10u32);
+        assert_eq!(a % Int128::from(10u32), Int128::zero());
+        assert_eq!(a % Int128::from(2u32), Int128::zero());
+        assert_eq!(a % Int128::from(1u32), Int128::zero());
+        assert_eq!(a % Int128::from(3u32), Int128::from(1u32));
+        assert_eq!(a % Int128::from(4u32), Int128::from(2u32));
+
+        assert_eq!(
+            Int128::from(-12i32) % Int128::from(10i32),
+            Int128::from(-2i32)
+        );
+        assert_eq!(
+            Int128::from(12i32) % Int128::from(-10i32),
+            Int128::from(2i32)
+        );
+        assert_eq!(
+            Int128::from(-12i32) % Int128::from(-10i32),
+            Int128::from(-2i32)
+        );
+
+        // works for refs
+        let a = Int128::from(10u32);
+        let b = Int128::from(3u32);
+        let expected = Int128::from(1u32);
+        assert_eq!(a % b, expected);
+        assert_eq!(a % &b, expected);
+        assert_eq!(&a % b, expected);
+        assert_eq!(&a % &b, expected);
+    }
+
+    #[test]
+    #[should_panic(expected = "divisor of zero")]
+    fn int128_rem_panics_for_zero() {
+        let _ = Int128::from(10u32) % Int128::zero();
+    }
+
+    #[test]
+    fn int128_rem_assign_works() {
+        let mut a = Int128::from(30u32);
+        a %= Int128::from(4u32);
+        assert_eq!(a, Int128::from(2u32));
+
+        // works for refs
+        let mut a = Int128::from(25u32);
+        let b = Int128::from(6u32);
+        a %= &b;
+        assert_eq!(a, Int128::from(1u32));
+    }
+
+    #[test]
+    fn int128_shr() {
+        let x: Int128 = 0x4000_0000_0000_0000_0000_0000_0000_0000i128.into();
+        assert_eq!(x >> 0, x); // right shift by 0 should be no-op
+        assert_eq!(
+            x >> 1,
+            Int128::from(0x2000_0000_0000_0000_0000_0000_0000_0000i128)
+        );
+        assert_eq!(
+            x >> 4,
+            Int128::from(0x0400_0000_0000_0000_0000_0000_0000_0000i128)
+        );
+        // right shift of MIN value by the maximum shift value should result in -1 (filled with 1s)
+        assert_eq!(
+            Int128::MIN >> (std::mem::size_of::<Int128>() as u32 * 8 - 1),
+            -Int128::one()
+        );
+    }
+
+    #[test]
+    fn int128_shl() {
+        let x: Int128 = 0x0800_0000_0000_0000_0000_0000_0000_0000i128.into();
+        assert_eq!(x << 0, x); // left shift by 0 should be no-op
+        assert_eq!(
+            x << 1,
+            Int128::from(0x1000_0000_0000_0000_0000_0000_0000_0000i128)
+        );
+        assert_eq!(
+            x << 4,
+            Int128::from(0x0800_0000_0000_0000_0000_0000_0000_0000i128 << 4)
+        );
+        // left shift by by the maximum shift value should result in MIN
+        assert_eq!(
+            Int128::one() << (std::mem::size_of::<Int128>() as u32 * 8 - 1),
+            Int128::MIN
+        );
+    }
+
+    #[test]
+    fn int128_abs_diff_works() {
+        let a = Int128::from(42u32);
+        let b = Int128::from(5u32);
+        let expected = Uint128::from(37u32);
+        assert_eq!(a.abs_diff(b), expected);
+        assert_eq!(b.abs_diff(a), expected);
+
+        let c = Int128::from(-5i32);
+        assert_eq!(b.abs_diff(c), Uint128::from(10u32));
+        assert_eq!(c.abs_diff(b), Uint128::from(10u32));
+    }
+
+    #[test]
+    #[should_panic = "attempt to negate with overflow"]
+    fn int128_neg_min_panics() {
+        _ = -Int128::MIN;
+    }
+
+    #[test]
+    fn int128_partial_eq() {
+        let test_cases = [(1, 1, true), (42, 42, true), (42, 24, false), (0, 0, true)]
+            .into_iter()
+            .map(|(lhs, rhs, expected): (u64, u64, bool)| {
+                (Int128::from(lhs), Int128::from(rhs), expected)
+            });
+
+        #[allow(clippy::op_ref)]
+        for (lhs, rhs, expected) in test_cases {
+            assert_eq!(lhs == rhs, expected);
+            assert_eq!(&lhs == rhs, expected);
+            assert_eq!(lhs == &rhs, expected);
+            assert_eq!(&lhs == &rhs, expected);
+        }
+    }
+}
diff --git a/packages/std/src/math/int256.rs b/packages/std/src/math/int256.rs
new file mode 100644
index 000000000..74ace49ac
--- /dev/null
+++ b/packages/std/src/math/int256.rs
@@ -0,0 +1,1183 @@
+use forward_ref::{forward_ref_binop, forward_ref_op_assign};
+use schemars::JsonSchema;
+use serde::{de, ser, Deserialize, Deserializer, Serialize};
+use std::fmt;
+use std::ops::{
+    Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Not, Rem, RemAssign, Shl, ShlAssign, Shr,
+    ShrAssign, Sub, SubAssign,
+};
+use std::str::FromStr;
+
+use crate::errors::{DivideByZeroError, DivisionError, OverflowError, OverflowOperation, StdError};
+use crate::{forward_ref_partial_eq, Int128, Int64, Uint128, Uint256, Uint64};
+
+/// Used internally - we don't want to leak this type since we might change
+/// the implementation in the future.
+use bnum::types::{I256, U256};
+
+/// An implementation of i256 that is using strings for JSON encoding/decoding,
+/// such that the full i256 range can be used for clients that convert JSON numbers to floats,
+/// like JavaScript and jq.
+///
+/// # Examples
+///
+/// Use `from` to create instances out of primitive uint types or `new` to provide big
+/// endian bytes:
+///
+/// ```
+/// # use cosmwasm_std::Int256;
+/// let a = Int256::from(258u128);
+/// let b = Int256::new([
+///     0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8,
+///     0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8,
+///     0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8,
+///     0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 1u8, 2u8,
+/// ]);
+/// assert_eq!(a, b);
+/// ```
+#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, JsonSchema)]
+pub struct Int256(#[schemars(with = "String")] I256);
+
+forward_ref_partial_eq!(Int256, Int256);
+
+impl Int256 {
+    pub const MAX: Int256 = Int256(I256::MAX);
+    pub const MIN: Int256 = Int256(I256::MIN);
+
+    /// Creates a Int256(value) from a big endian representation. It's just an alias for
+    /// `from_be_bytes`.
+    #[inline]
+    pub const fn new(value: [u8; 32]) -> Self {
+        Self::from_be_bytes(value)
+    }
+
+    /// Creates a Int256(0)
+    #[inline]
+    pub const fn zero() -> Self {
+        Int256(I256::ZERO)
+    }
+
+    /// Creates a Int256(1)
+    #[inline]
+    pub const fn one() -> Self {
+        Self(I256::ONE)
+    }
+
+    #[must_use]
+    pub const fn from_be_bytes(data: [u8; 32]) -> Self {
+        let words: [u64; 4] = [
+            u64::from_le_bytes([
+                data[31], data[30], data[29], data[28], data[27], data[26], data[25], data[24],
+            ]),
+            u64::from_le_bytes([
+                data[23], data[22], data[21], data[20], data[19], data[18], data[17], data[16],
+            ]),
+            u64::from_le_bytes([
+                data[15], data[14], data[13], data[12], data[11], data[10], data[9], data[8],
+            ]),
+            u64::from_le_bytes([
+                data[7], data[6], data[5], data[4], data[3], data[2], data[1], data[0],
+            ]),
+        ];
+        Self(I256::from_bits(U256::from_digits(words)))
+    }
+
+    #[must_use]
+    pub const fn from_le_bytes(data: [u8; 32]) -> Self {
+        let words: [u64; 4] = [
+            u64::from_le_bytes([
+                data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
+            ]),
+            u64::from_le_bytes([
+                data[8], data[9], data[10], data[11], data[12], data[13], data[14], data[15],
+            ]),
+            u64::from_le_bytes([
+                data[16], data[17], data[18], data[19], data[20], data[21], data[22], data[23],
+            ]),
+            u64::from_le_bytes([
+                data[24], data[25], data[26], data[27], data[28], data[29], data[30], data[31],
+            ]),
+        ];
+        Self(I256::from_bits(U256::from_digits(words)))
+    }
+
+    /// Returns a copy of the number as big endian bytes.
+    #[must_use = "this returns the result of the operation, without modifying the original"]
+    pub const fn to_be_bytes(self) -> [u8; 32] {
+        let bits = self.0.to_bits();
+        let words = bits.digits();
+        let words = [
+            words[3].to_be_bytes(),
+            words[2].to_be_bytes(),
+            words[1].to_be_bytes(),
+            words[0].to_be_bytes(),
+        ];
+        unsafe { std::mem::transmute::<[[u8; 8]; 4], [u8; 32]>(words) }
+    }
+
+    /// Returns a copy of the number as little endian bytes.
+    #[must_use = "this returns the result of the operation, without modifying the original"]
+    pub const fn to_le_bytes(self) -> [u8; 32] {
+        let bits = self.0.to_bits();
+        let words = bits.digits();
+        let words = [
+            words[0].to_le_bytes(),
+            words[1].to_le_bytes(),
+            words[2].to_le_bytes(),
+            words[3].to_le_bytes(),
+        ];
+        unsafe { std::mem::transmute::<[[u8; 8]; 4], [u8; 32]>(words) }
+    }
+
+    #[must_use]
+    pub const fn is_zero(&self) -> bool {
+        self.0.is_zero()
+    }
+
+    #[must_use = "this returns the result of the operation, without modifying the original"]
+    pub fn pow(self, exp: u32) -> Self {
+        Self(self.0.pow(exp))
+    }
+
+    pub fn checked_add(self, other: Self) -> Result<Self, OverflowError> {
+        self.0
+            .checked_add(other.0)
+            .map(Self)
+            .ok_or_else(|| OverflowError::new(OverflowOperation::Add, self, other))
+    }
+
+    pub fn checked_sub(self, other: Self) -> Result<Self, OverflowError> {
+        self.0
+            .checked_sub(other.0)
+            .map(Self)
+            .ok_or_else(|| OverflowError::new(OverflowOperation::Sub, self, other))
+    }
+
+    pub fn checked_mul(self, other: Self) -> Result<Self, OverflowError> {
+        self.0
+            .checked_mul(other.0)
+            .map(Self)
+            .ok_or_else(|| OverflowError::new(OverflowOperation::Mul, self, other))
+    }
+
+    pub fn checked_pow(self, exp: u32) -> Result<Self, OverflowError> {
+        self.0
+            .checked_pow(exp)
+            .map(Self)
+            .ok_or_else(|| OverflowError::new(OverflowOperation::Pow, self, exp))
+    }
+
+    pub fn checked_div(self, other: Self) -> Result<Self, DivisionError> {
+        if other.is_zero() {
+            return Err(DivisionError::DivideByZero);
+        }
+        self.0
+            .checked_div(other.0)
+            .map(Self)
+            .ok_or(DivisionError::Overflow)
+    }
+
+    pub fn checked_div_euclid(self, other: Self) -> Result<Self, DivisionError> {
+        if other.is_zero() {
+            return Err(DivisionError::DivideByZero);
+        }
+        self.0
+            .checked_div_euclid(other.0)
+            .map(Self)
+            .ok_or(DivisionError::Overflow)
+    }
+
+    pub fn checked_rem(self, other: Self) -> Result<Self, DivideByZeroError> {
+        self.0
+            .checked_rem(other.0)
+            .map(Self)
+            .ok_or_else(|| DivideByZeroError::new(self))
+    }
+
+    pub fn checked_shr(self, other: u32) -> Result<Self, OverflowError> {
+        if other >= 256 {
+            return Err(OverflowError::new(OverflowOperation::Shr, self, other));
+        }
+
+        Ok(Self(self.0.shr(other)))
+    }
+
+    pub fn checked_shl(self, other: u32) -> Result<Self, OverflowError> {
+        if other >= 256 {
+            return Err(OverflowError::new(OverflowOperation::Shl, self, other));
+        }
+
+        Ok(Self(self.0.shl(other)))
+    }
+
+    #[must_use = "this returns the result of the operation, without modifying the original"]
+    #[inline]
+    pub fn wrapping_add(self, other: Self) -> Self {
+        Self(self.0.wrapping_add(other.0))
+    }
+
+    #[must_use = "this returns the result of the operation, without modifying the original"]
+    #[inline]
+    pub fn wrapping_sub(self, other: Self) -> Self {
+        Self(self.0.wrapping_sub(other.0))
+    }
+
+    #[must_use = "this returns the result of the operation, without modifying the original"]
+    #[inline]
+    pub fn wrapping_mul(self, other: Self) -> Self {
+        Self(self.0.wrapping_mul(other.0))
+    }
+
+    #[must_use = "this returns the result of the operation, without modifying the original"]
+    #[inline]
+    pub fn wrapping_pow(self, other: u32) -> Self {
+        Self(self.0.wrapping_pow(other))
+    }
+
+    #[must_use = "this returns the result of the operation, without modifying the original"]
+    pub fn saturating_add(self, other: Self) -> Self {
+        Self(self.0.saturating_add(other.0))
+    }
+
+    #[must_use = "this returns the result of the operation, without modifying the original"]
+    pub fn saturating_sub(self, other: Self) -> Self {
+        Self(self.0.saturating_sub(other.0))
+    }
+
+    #[must_use = "this returns the result of the operation, without modifying the original"]
+    pub fn saturating_mul(self, other: Self) -> Self {
+        Self(self.0.saturating_mul(other.0))
+    }
+
+    #[must_use = "this returns the result of the operation, without modifying the original"]
+    pub fn saturating_pow(self, exp: u32) -> Self {
+        Self(self.0.saturating_pow(exp))
+    }
+
+    #[must_use = "this returns the result of the operation, without modifying the original"]
+    pub fn abs_diff(self, other: Self) -> Uint256 {
+        Uint256(self.0.abs_diff(other.0))
+    }
+}
+
+impl From<Uint128> for Int256 {
+    fn from(val: Uint128) -> Self {
+        val.u128().into()
+    }
+}
+
+impl From<Uint64> for Int256 {
+    fn from(val: Uint64) -> Self {
+        val.u64().into()
+    }
+}
+
+impl From<u128> for Int256 {
+    fn from(val: u128) -> Self {
+        Int256(val.into())
+    }
+}
+
+impl From<u64> for Int256 {
+    fn from(val: u64) -> Self {
+        Int256(val.into())
+    }
+}
+
+impl From<u32> for Int256 {
+    fn from(val: u32) -> Self {
+        Int256(val.into())
+    }
+}
+
+impl From<u16> for Int256 {
+    fn from(val: u16) -> Self {
+        Int256(val.into())
+    }
+}
+
+impl From<u8> for Int256 {
+    fn from(val: u8) -> Self {
+        Int256(val.into())
+    }
+}
+
+impl From<Int128> for Int256 {
+    fn from(val: Int128) -> Self {
+        val.i128().into()
+    }
+}
+
+impl From<Int64> for Int256 {
+    fn from(val: Int64) -> Self {
+        val.i64().into()
+    }
+}
+
+impl From<i128> for Int256 {
+    fn from(val: i128) -> Self {
+        Int256(val.into())
+    }
+}
+
+impl From<i64> for Int256 {
+    fn from(val: i64) -> Self {
+        Int256(val.into())
+    }
+}
+
+impl From<i32> for Int256 {
+    fn from(val: i32) -> Self {
+        Int256(val.into())
+    }
+}
+
+impl From<i16> for Int256 {
+    fn from(val: i16) -> Self {
+        Int256(val.into())
+    }
+}
+
+impl From<i8> for Int256 {
+    fn from(val: i8) -> Self {
+        Int256(val.into())
+    }
+}
+
+impl TryFrom<&str> for Int256 {
+    type Error = StdError;
+
+    fn try_from(val: &str) -> Result<Self, Self::Error> {
+        Self::from_str(val)
+    }
+}
+
+impl FromStr for Int256 {
+    type Err = StdError;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        match I256::from_str_radix(s, 10) {
+            Ok(u) => Ok(Self(u)),
+            Err(e) => Err(StdError::generic_err(format!("Parsing Int256: {e}"))),
+        }
+    }
+}
+
+impl From<Int256> for String {
+    fn from(original: Int256) -> Self {
+        original.to_string()
+    }
+}
+
+impl fmt::Display for Int256 {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        // The inner type doesn't work as expected with padding, so we
+        // work around that. Remove this code when the upstream padding is fixed.
+        let unpadded = self.0.to_string();
+        let numeric = unpadded.strip_prefix('-').unwrap_or(&unpadded);
+
+        f.pad_integral(self >= &Self::zero(), "", numeric)
+    }
+}
+
+impl Add<Int256> for Int256 {
+    type Output = Self;
+
+    fn add(self, rhs: Self) -> Self {
+        Int256(self.0.checked_add(rhs.0).unwrap())
+    }
+}
+forward_ref_binop!(impl Add, add for Int256, Int256);
+
+impl Sub<Int256> for Int256 {
+    type Output = Self;
+
+    fn sub(self, rhs: Self) -> Self {
+        Int256(self.0.checked_sub(rhs.0).unwrap())
+    }
+}
+forward_ref_binop!(impl Sub, sub for Int256, Int256);
+
+impl SubAssign<Int256> for Int256 {
+    fn sub_assign(&mut self, rhs: Int256) {
+        self.0 = self.0.checked_sub(rhs.0).unwrap();
+    }
+}
+forward_ref_op_assign!(impl SubAssign, sub_assign for Int256, Int256);
+
+impl Div<Int256> for Int256 {
+    type Output = Self;
+
+    fn div(self, rhs: Self) -> Self::Output {
+        Self(self.0.checked_div(rhs.0).unwrap())
+    }
+}
+forward_ref_binop!(impl Div, div for Int256, Int256);
+
+impl Rem for Int256 {
+    type Output = Self;
+
+    /// # Panics
+    ///
+    /// This operation will panic if `rhs` is zero.
+    #[inline]
+    fn rem(self, rhs: Self) -> Self {
+        Self(self.0.rem(rhs.0))
+    }
+}
+forward_ref_binop!(impl Rem, rem for Int256, Int256);
+
+impl Not for Int256 {
+    type Output = Self;
+
+    fn not(self) -> Self::Output {
+        Self(!self.0)
+    }
+}
+
+impl Neg for Int256 {
+    type Output = Self;
+
+    fn neg(self) -> Self::Output {
+        Self(-self.0)
+    }
+}
+
+impl RemAssign<Int256> for Int256 {
+    fn rem_assign(&mut self, rhs: Int256) {
+        *self = *self % rhs;
+    }
+}
+forward_ref_op_assign!(impl RemAssign, rem_assign for Int256, Int256);
+
+impl Mul<Int256> for Int256 {
+    type Output = Self;
+
+    fn mul(self, rhs: Self) -> Self::Output {
+        Self(self.0.checked_mul(rhs.0).unwrap())
+    }
+}
+forward_ref_binop!(impl Mul, mul for Int256, Int256);
+
+impl MulAssign<Int256> for Int256 {
+    fn mul_assign(&mut self, rhs: Self) {
+        self.0 = self.0.checked_mul(rhs.0).unwrap();
+    }
+}
+forward_ref_op_assign!(impl MulAssign, mul_assign for Int256, Int256);
+
+impl Shr<u32> for Int256 {
+    type Output = Self;
+
+    fn shr(self, rhs: u32) -> Self::Output {
+        self.checked_shr(rhs).unwrap_or_else(|_| {
+            panic!("right shift error: {rhs} is larger or equal than the number of bits in Int256",)
+        })
+    }
+}
+forward_ref_binop!(impl Shr, shr for Int256, u32);
+
+impl Shl<u32> for Int256 {
+    type Output = Self;
+
+    fn shl(self, rhs: u32) -> Self::Output {
+        self.checked_shl(rhs).unwrap_or_else(|_| {
+            panic!("left shift error: {rhs} is larger or equal than the number of bits in Int256",)
+        })
+    }
+}
+forward_ref_binop!(impl Shl, shl for Int256, u32);
+
+impl AddAssign<Int256> for Int256 {
+    fn add_assign(&mut self, rhs: Int256) {
+        self.0 = self.0.checked_add(rhs.0).unwrap();
+    }
+}
+forward_ref_op_assign!(impl AddAssign, add_assign for Int256, Int256);
+
+impl DivAssign<Int256> for Int256 {
+    fn div_assign(&mut self, rhs: Self) {
+        self.0 = self.0.checked_div(rhs.0).unwrap();
+    }
+}
+forward_ref_op_assign!(impl DivAssign, div_assign for Int256, Int256);
+
+impl ShrAssign<u32> for Int256 {
+    fn shr_assign(&mut self, rhs: u32) {
+        *self = Shr::<u32>::shr(*self, rhs);
+    }
+}
+forward_ref_op_assign!(impl ShrAssign, shr_assign for Int256, u32);
+
+impl ShlAssign<u32> for Int256 {
+    fn shl_assign(&mut self, rhs: u32) {
+        *self = Shl::<u32>::shl(*self, rhs);
+    }
+}
+forward_ref_op_assign!(impl ShlAssign, shl_assign for Int256, u32);
+
+impl Serialize for Int256 {
+    /// Serializes as an integer string using base 10
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: ser::Serializer,
+    {
+        serializer.serialize_str(&self.to_string())
+    }
+}
+
+impl<'de> Deserialize<'de> for Int256 {
+    /// Deserialized from an integer string using base 10
+    fn deserialize<D>(deserializer: D) -> Result<Int256, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        deserializer.deserialize_str(Int256Visitor)
+    }
+}
+
+struct Int256Visitor;
+
+impl<'de> de::Visitor<'de> for Int256Visitor {
+    type Value = Int256;
+
+    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+        formatter.write_str("string-encoded integer")
+    }
+
+    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
+    where
+        E: de::Error,
+    {
+        Int256::try_from(v).map_err(|e| E::custom(format!("invalid Int256 '{v}' - {e}")))
+    }
+}
+
+impl<A> std::iter::Sum<A> for Int256
+where
+    Self: Add<A, Output = Self>,
+{
+    fn sum<I: Iterator<Item = A>>(iter: I) -> Self {
+        iter.fold(Self::zero(), Add::add)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::{from_slice, to_vec};
+
+    #[test]
+    fn size_of_works() {
+        assert_eq!(std::mem::size_of::<Int256>(), 32);
+    }
+
+    #[test]
+    fn int256_new_works() {
+        let num = Int256::new([1; 32]);
+        let a: [u8; 32] = num.to_be_bytes();
+        assert_eq!(a, [1; 32]);
+
+        let be_bytes = [
+            0u8, 222u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8,
+            0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 1u8, 2u8, 3u8,
+        ];
+        let num = Int256::new(be_bytes);
+        let resulting_bytes: [u8; 32] = num.to_be_bytes();
+        assert_eq!(be_bytes, resulting_bytes);
+    }
+
+    #[test]
+    fn int256_zero_works() {
+        let zero = Int256::zero();
+        assert_eq!(zero.to_be_bytes(), [0; 32]);
+    }
+
+    #[test]
+    fn uint256_one_works() {
+        let one = Int256::one();
+        let mut one_be = [0; 32];
+        one_be[31] = 1;
+
+        assert_eq!(one.to_be_bytes(), one_be);
+    }
+
+    #[test]
+    fn int256_endianness() {
+        let be_bytes = [
+            0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8,
+            0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 1u8, 2u8, 3u8,
+        ];
+        let le_bytes = [
+            3u8, 2u8, 1u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8,
+            0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8,
+        ];
+
+        // These should all be the same.
+        let num1 = Int256::new(be_bytes);
+        let num2 = Int256::from_be_bytes(be_bytes);
+        let num3 = Int256::from_le_bytes(le_bytes);
+        assert_eq!(num1, Int256::from(65536u32 + 512 + 3));
+        assert_eq!(num1, num2);
+        assert_eq!(num1, num3);
+    }
+
+    #[test]
+    fn int256_convert_from() {
+        let a = Int256::from(5u128);
+        assert_eq!(a.0, I256::from(5u32));
+
+        let a = Int256::from(5u64);
+        assert_eq!(a.0, I256::from(5u32));
+
+        let a = Int256::from(5u32);
+        assert_eq!(a.0, I256::from(5u32));
+
+        let a = Int256::from(5u16);
+        assert_eq!(a.0, I256::from(5u32));
+
+        let a = Int256::from(5u8);
+        assert_eq!(a.0, I256::from(5u32));
+
+        let a = Int256::from(-5i128);
+        assert_eq!(a.0, I256::from(-5i32));
+
+        let a = Int256::from(-5i64);
+        assert_eq!(a.0, I256::from(-5i32));
+
+        let a = Int256::from(-5i32);
+        assert_eq!(a.0, I256::from(-5i32));
+
+        let a = Int256::from(-5i16);
+        assert_eq!(a.0, I256::from(-5i32));
+
+        let a = Int256::from(-5i8);
+        assert_eq!(a.0, I256::from(-5i32));
+
+        let result = Int256::try_from("34567");
+        assert_eq!(
+            result.unwrap().0,
+            I256::from_str_radix("34567", 10).unwrap()
+        );
+
+        let result = Int256::try_from("1.23");
+        assert!(result.is_err());
+    }
+
+    #[test]
+    fn int256_implements_display() {
+        let a = Int256::from(12345u32);
+        assert_eq!(format!("Embedded: {a}"), "Embedded: 12345");
+        assert_eq!(a.to_string(), "12345");
+
+        let a = Int256::from(-12345i32);
+        assert_eq!(format!("Embedded: {a}"), "Embedded: -12345");
+        assert_eq!(a.to_string(), "-12345");
+
+        let a = Int256::zero();
+        assert_eq!(format!("Embedded: {a}"), "Embedded: 0");
+        assert_eq!(a.to_string(), "0");
+    }
+
+    #[test]
+    fn int256_display_padding_works() {
+        let a = Int256::from(123u64);
+        assert_eq!(format!("Embedded: {a:05}"), "Embedded: 00123");
+
+        let a = Int256::from(-123i64);
+        assert_eq!(format!("Embedded: {a:05}"), "Embedded: -0123");
+    }
+
+    #[test]
+    fn int256_to_be_bytes_works() {
+        assert_eq!(Int256::zero().to_be_bytes(), [0; 32]);
+
+        let mut max = [0xff; 32];
+        max[0] = 0x7f;
+        assert_eq!(Int256::MAX.to_be_bytes(), max);
+
+        let mut one = [0; 32];
+        one[31] = 1;
+        assert_eq!(Int256::from(1u128).to_be_bytes(), one);
+        // Python: `[b for b in (240282366920938463463374607431768124608).to_bytes(32, "big")]`
+        assert_eq!(
+            Int256::from(240282366920938463463374607431768124608u128).to_be_bytes(),
+            [
+                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 180, 196, 179, 87, 165, 121, 59,
+                133, 246, 117, 221, 191, 255, 254, 172, 192
+            ]
+        );
+        assert_eq!(
+            Int256::from_be_bytes([
+                17, 4, 23, 32, 87, 67, 123, 200, 58, 91, 0, 38, 33, 21, 67, 78, 87, 76, 65, 54,
+                211, 201, 192, 7, 42, 233, 2, 240, 200, 115, 150, 240
+            ])
+            .to_be_bytes(),
+            [
+                17, 4, 23, 32, 87, 67, 123, 200, 58, 91, 0, 38, 33, 21, 67, 78, 87, 76, 65, 54,
+                211, 201, 192, 7, 42, 233, 2, 240, 200, 115, 150, 240
+            ]
+        );
+    }
+
+    #[test]
+    fn int256_to_le_bytes_works() {
+        assert_eq!(Int256::zero().to_le_bytes(), [0; 32]);
+
+        let mut max = [0xff; 32];
+        max[31] = 0x7f;
+        assert_eq!(Int256::MAX.to_le_bytes(), max);
+
+        let mut one = [0; 32];
+        one[0] = 1;
+        assert_eq!(Int256::from(1u128).to_le_bytes(), one);
+        // Python: `[b for b in (240282366920938463463374607431768124608).to_bytes(64, "little")]`
+        assert_eq!(
+            Int256::from(240282366920938463463374607431768124608u128).to_le_bytes(),
+            [
+                192, 172, 254, 255, 191, 221, 117, 246, 133, 59, 121, 165, 87, 179, 196, 180, 0, 0,
+                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+            ]
+        );
+        assert_eq!(
+            Int256::from_be_bytes([
+                17, 4, 23, 32, 87, 67, 123, 200, 58, 91, 0, 38, 33, 21, 67, 78, 87, 76, 65, 54,
+                211, 201, 192, 7, 42, 233, 2, 240, 200, 115, 150, 240
+            ])
+            .to_le_bytes(),
+            [
+                240, 150, 115, 200, 240, 2, 233, 42, 7, 192, 201, 211, 54, 65, 76, 87, 78, 67, 21,
+                33, 38, 0, 91, 58, 200, 123, 67, 87, 32, 23, 4, 17
+            ]
+        );
+    }
+
+    #[test]
+    fn int256_is_zero_works() {
+        assert!(Int256::zero().is_zero());
+        assert!(Int256(I256::from(0u32)).is_zero());
+
+        assert!(!Int256::from(1u32).is_zero());
+        assert!(!Int256::from(123u32).is_zero());
+        assert!(!Int256::from(-123i32).is_zero());
+    }
+
+    #[test]
+    fn int256_wrapping_methods() {
+        // wrapping_add
+        assert_eq!(
+            Int256::from(2u32).wrapping_add(Int256::from(2u32)),
+            Int256::from(4u32)
+        ); // non-wrapping
+        assert_eq!(Int256::MAX.wrapping_add(Int256::from(1u32)), Int256::MIN); // wrapping
+
+        // wrapping_sub
+        assert_eq!(
+            Int256::from(7u32).wrapping_sub(Int256::from(5u32)),
+            Int256::from(2u32)
+        ); // non-wrapping
+        assert_eq!(Int256::MIN.wrapping_sub(Int256::from(1u32)), Int256::MAX); // wrapping
+
+        // wrapping_mul
+        assert_eq!(
+            Int256::from(3u32).wrapping_mul(Int256::from(2u32)),
+            Int256::from(6u32)
+        ); // non-wrapping
+        assert_eq!(
+            Int256::MAX.wrapping_mul(Int256::from(2u32)),
+            Int256::from(-2i32)
+        ); // wrapping
+
+        // wrapping_pow
+        assert_eq!(Int256::from(2u32).wrapping_pow(3), Int256::from(8u32)); // non-wrapping
+        assert_eq!(Int256::MAX.wrapping_pow(2), Int256::from(1u32)); // wrapping
+    }
+
+    #[test]
+    fn int256_json() {
+        let orig = Int256::from(1234567890987654321u128);
+        let serialized = to_vec(&orig).unwrap();
+        assert_eq!(serialized.as_slice(), b"\"1234567890987654321\"");
+        let parsed: Int256 = from_slice(&serialized).unwrap();
+        assert_eq!(parsed, orig);
+    }
+
+    #[test]
+    fn int256_compare() {
+        let a = Int256::from(12345u32);
+        let b = Int256::from(23456u32);
+
+        assert!(a < b);
+        assert!(b > a);
+        assert_eq!(a, Int256::from(12345u32));
+    }
+
+    #[test]
+    #[allow(clippy::op_ref)]
+    fn int256_math() {
+        let a = Int256::from(-12345i32);
+        let b = Int256::from(23456u32);
+
+        // test + with owned and reference right hand side
+        assert_eq!(a + b, Int256::from(11111u32));
+        assert_eq!(a + &b, Int256::from(11111u32));
+
+        // test - with owned and reference right hand side
+        assert_eq!(b - a, Int256::from(35801u32));
+        assert_eq!(b - &a, Int256::from(35801u32));
+
+        // test += with owned and reference right hand side
+        let mut c = Int256::from(300000u32);
+        c += b;
+        assert_eq!(c, Int256::from(323456u32));
+        let mut d = Int256::from(300000u32);
+        d += &b;
+        assert_eq!(d, Int256::from(323456u32));
+
+        // test -= with owned and reference right hand side
+        let mut c = Int256::from(300000u32);
+        c -= b;
+        assert_eq!(c, Int256::from(276544u32));
+        let mut d = Int256::from(300000u32);
+        d -= &b;
+        assert_eq!(d, Int256::from(276544u32));
+
+        // test - with negative result
+        assert_eq!(a - b, Int256::from(-35801i32));
+    }
+
+    #[test]
+    #[should_panic]
+    fn int256_add_overflow_panics() {
+        let _ = Int256::MAX + Int256::from(12u32);
+    }
+
+    #[test]
+    #[allow(clippy::op_ref)]
+    fn int256_sub_works() {
+        assert_eq!(Int256::from(2u32) - Int256::from(1u32), Int256::from(1u32));
+        assert_eq!(Int256::from(2u32) - Int256::from(0u32), Int256::from(2u32));
+        assert_eq!(Int256::from(2u32) - Int256::from(2u32), Int256::from(0u32));
+        assert_eq!(Int256::from(2u32) - Int256::from(3u32), Int256::from(-1i32));
+
+        // works for refs
+        let a = Int256::from(10u32);
+        let b = Int256::from(3u32);
+        let expected = Int256::from(7u32);
+        assert_eq!(a - b, expected);
+        assert_eq!(a - &b, expected);
+        assert_eq!(&a - b, expected);
+        assert_eq!(&a - &b, expected);
+    }
+
+    #[test]
+    #[should_panic]
+    fn int256_sub_overflow_panics() {
+        let _ = Int256::MIN + Int256::one() - Int256::from(2u32);
+    }
+
+    #[test]
+    fn int256_sub_assign_works() {
+        let mut a = Int256::from(14u32);
+        a -= Int256::from(2u32);
+        assert_eq!(a, Int256::from(12u32));
+
+        // works for refs
+        let mut a = Int256::from(10u32);
+        let b = Int256::from(3u32);
+        let expected = Int256::from(7u32);
+        a -= &b;
+        assert_eq!(a, expected);
+    }
+
+    #[test]
+    #[allow(clippy::op_ref)]
+    fn int256_mul_works() {
+        assert_eq!(Int256::from(2u32) * Int256::from(3u32), Int256::from(6u32));
+        assert_eq!(Int256::from(2u32) * Int256::zero(), Int256::zero());
+
+        // works for refs
+        let a = Int256::from(11u32);
+        let b = Int256::from(3u32);
+        let expected = Int256::from(33u32);
+        assert_eq!(a * b, expected);
+        assert_eq!(a * &b, expected);
+        assert_eq!(&a * b, expected);
+        assert_eq!(&a * &b, expected);
+    }
+
+    #[test]
+    fn int256_mul_assign_works() {
+        let mut a = Int256::from(14u32);
+        a *= Int256::from(2u32);
+        assert_eq!(a, Int256::from(28u32));
+
+        // works for refs
+        let mut a = Int256::from(10u32);
+        let b = Int256::from(3u32);
+        a *= &b;
+        assert_eq!(a, Int256::from(30u32));
+    }
+
+    #[test]
+    fn int256_pow_works() {
+        assert_eq!(Int256::from(2u32).pow(2), Int256::from(4u32));
+        assert_eq!(Int256::from(2u32).pow(10), Int256::from(1024u32));
+    }
+
+    #[test]
+    #[should_panic]
+    fn int256_pow_overflow_panics() {
+        _ = Int256::MAX.pow(2u32);
+    }
+
+    #[test]
+    fn int256_shr_works() {
+        let original = Int256::new([
+            0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8,
+            0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 2u8, 0u8, 4u8, 2u8,
+        ]);
+
+        let shifted = Int256::new([
+            0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8,
+            0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 128u8, 1u8, 0u8,
+        ]);
+
+        assert_eq!(original >> 2u32, shifted);
+    }
+
+    #[test]
+    #[should_panic]
+    fn int256_shr_overflow_panics() {
+        let _ = Int256::from(1u32) >> 256u32;
+    }
+
+    #[test]
+    fn sum_works() {
+        let nums = vec![
+            Int256::from(17u32),
+            Int256::from(123u32),
+            Int256::from(540u32),
+            Int256::from(82u32),
+        ];
+        let expected = Int256::from(762u32);
+
+        let sum_as_ref: Int256 = nums.iter().sum();
+        assert_eq!(expected, sum_as_ref);
+
+        let sum_as_owned: Int256 = nums.into_iter().sum();
+        assert_eq!(expected, sum_as_owned);
+    }
+
+    #[test]
+    fn int256_methods() {
+        // checked_*
+        assert!(matches!(
+            Int256::MAX.checked_add(Int256::from(1u32)),
+            Err(OverflowError { .. })
+        ));
+        assert_eq!(
+            Int256::from(1u32).checked_add(Int256::from(1u32)),
+            Ok(Int256::from(2u32)),
+        );
+        assert!(matches!(
+            Int256::MIN.checked_sub(Int256::from(1u32)),
+            Err(OverflowError { .. })
+        ));
+        assert_eq!(
+            Int256::from(2u32).checked_sub(Int256::from(1u32)),
+            Ok(Int256::from(1u32)),
+        );
+        assert!(matches!(
+            Int256::MAX.checked_mul(Int256::from(2u32)),
+            Err(OverflowError { .. })
+        ));
+        assert_eq!(
+            Int256::from(2u32).checked_mul(Int256::from(2u32)),
+            Ok(Int256::from(4u32)),
+        );
+        assert!(matches!(
+            Int256::MAX.checked_pow(2u32),
+            Err(OverflowError { .. })
+        ));
+        assert_eq!(Int256::from(2u32).checked_pow(3u32), Ok(Int256::from(8u32)),);
+        assert_eq!(
+            Int256::MAX.checked_div(Int256::from(0u32)),
+            Err(DivisionError::DivideByZero)
+        );
+        assert_eq!(
+            Int256::from(6u32).checked_div(Int256::from(2u32)),
+            Ok(Int256::from(3u32)),
+        );
+        assert_eq!(
+            Int256::MAX.checked_div_euclid(Int256::from(0u32)),
+            Err(DivisionError::DivideByZero)
+        );
+        assert_eq!(
+            Int256::from(6u32).checked_div_euclid(Int256::from(2u32)),
+            Ok(Int256::from(3u32)),
+        );
+        assert_eq!(
+            Int256::from(7u32).checked_div_euclid(Int256::from(2u32)),
+            Ok(Int256::from(3u32)),
+        );
+        assert!(matches!(
+            Int256::MAX.checked_rem(Int256::from(0u32)),
+            Err(DivideByZeroError { .. })
+        ));
+        // checked_* with negative numbers
+        assert_eq!(
+            Int256::from(-12i32).checked_div(Int256::from(10i32)),
+            Ok(Int256::from(-1i32)),
+        );
+        assert_eq!(
+            Int256::from(-2i32).checked_pow(3u32),
+            Ok(Int256::from(-8i32)),
+        );
+        assert_eq!(
+            Int256::from(-6i32).checked_mul(Int256::from(-7i32)),
+            Ok(Int256::from(42i32)),
+        );
+        assert_eq!(
+            Int256::from(-2i32).checked_add(Int256::from(3i32)),
+            Ok(Int256::from(1i32)),
+        );
+        assert_eq!(
+            Int256::from(-1i32).checked_div_euclid(Int256::from(-2i32)),
+            Ok(Int256::from(1u32)),
+        );
+
+        // saturating_*
+        assert_eq!(Int256::MAX.saturating_add(Int256::from(1u32)), Int256::MAX);
+        assert_eq!(Int256::MIN.saturating_sub(Int256::from(1u32)), Int256::MIN);
+        assert_eq!(Int256::MAX.saturating_mul(Int256::from(2u32)), Int256::MAX);
+        assert_eq!(Int256::from(4u32).saturating_pow(2u32), Int256::from(16u32));
+        assert_eq!(Int256::MAX.saturating_pow(2u32), Int256::MAX);
+    }
+
+    #[test]
+    #[allow(clippy::op_ref)]
+    fn int256_implements_rem() {
+        let a = Int256::from(10u32);
+        assert_eq!(a % Int256::from(10u32), Int256::zero());
+        assert_eq!(a % Int256::from(2u32), Int256::zero());
+        assert_eq!(a % Int256::from(1u32), Int256::zero());
+        assert_eq!(a % Int256::from(3u32), Int256::from(1u32));
+        assert_eq!(a % Int256::from(4u32), Int256::from(2u32));
+
+        assert_eq!(
+            Int256::from(-12i32) % Int256::from(10i32),
+            Int256::from(-2i32)
+        );
+        assert_eq!(
+            Int256::from(12i32) % Int256::from(-10i32),
+            Int256::from(2i32)
+        );
+        assert_eq!(
+            Int256::from(-12i32) % Int256::from(-10i32),
+            Int256::from(-2i32)
+        );
+
+        // works for refs
+        let a = Int256::from(10u32);
+        let b = Int256::from(3u32);
+        let expected = Int256::from(1u32);
+        assert_eq!(a % b, expected);
+        assert_eq!(a % &b, expected);
+        assert_eq!(&a % b, expected);
+        assert_eq!(&a % &b, expected);
+    }
+
+    #[test]
+    #[should_panic(expected = "divisor of zero")]
+    fn int256_rem_panics_for_zero() {
+        let _ = Int256::from(10u32) % Int256::zero();
+    }
+
+    #[test]
+    fn int256_rem_assign_works() {
+        let mut a = Int256::from(30u32);
+        a %= Int256::from(4u32);
+        assert_eq!(a, Int256::from(2u32));
+
+        // works for refs
+        let mut a = Int256::from(25u32);
+        let b = Int256::from(6u32);
+        a %= &b;
+        assert_eq!(a, Int256::from(1u32));
+    }
+
+    #[test]
+    fn int256_shr() {
+        let x: Int256 = 0x8000_0000_0000_0000_0000_0000_0000_0000u128.into();
+        assert_eq!(x >> 0, x); // right shift by 0 should be no-op
+        assert_eq!(
+            x >> 1,
+            Int256::from(0x4000_0000_0000_0000_0000_0000_0000_0000u128)
+        );
+        assert_eq!(
+            x >> 4,
+            Int256::from(0x0800_0000_0000_0000_0000_0000_0000_0000u128)
+        );
+        // right shift of MIN value by the maximum shift value should result in -1 (filled with 1s)
+        assert_eq!(
+            Int256::MIN >> (std::mem::size_of::<Int256>() as u32 * 8 - 1),
+            -Int256::one()
+        );
+    }
+
+    #[test]
+    fn int256_shl() {
+        let x: Int256 = 0x0800_0000_0000_0000_0000_0000_0000_0000u128.into();
+        assert_eq!(x << 0, x); // left shift by 0 should be no-op
+        assert_eq!(
+            x << 1,
+            Int256::from(0x1000_0000_0000_0000_0000_0000_0000_0000u128)
+        );
+        assert_eq!(
+            x << 4,
+            Int256::from(0x8000_0000_0000_0000_0000_0000_0000_0000u128)
+        );
+        // left shift by by the maximum shift value should result in MIN
+        assert_eq!(
+            Int256::one() << (std::mem::size_of::<Int256>() as u32 * 8 - 1),
+            Int256::MIN
+        );
+    }
+
+    #[test]
+    fn int256_abs_diff_works() {
+        let a = Int256::from(42u32);
+        let b = Int256::from(5u32);
+        let expected = Uint256::from(37u32);
+        assert_eq!(a.abs_diff(b), expected);
+        assert_eq!(b.abs_diff(a), expected);
+
+        let c = Int256::from(-5i32);
+        assert_eq!(b.abs_diff(c), Uint256::from(10u32));
+        assert_eq!(c.abs_diff(b), Uint256::from(10u32));
+    }
+
+    #[test]
+    #[should_panic = "attempt to negate with overflow"]
+    fn int256_neg_min_panics() {
+        _ = -Int256::MIN;
+    }
+
+    #[test]
+    fn int256_partial_eq() {
+        let test_cases = [(1, 1, true), (42, 42, true), (42, 24, false), (0, 0, true)]
+            .into_iter()
+            .map(|(lhs, rhs, expected): (u64, u64, bool)| {
+                (Int256::from(lhs), Int256::from(rhs), expected)
+            });
+
+        #[allow(clippy::op_ref)]
+        for (lhs, rhs, expected) in test_cases {
+            assert_eq!(lhs == rhs, expected);
+            assert_eq!(&lhs == rhs, expected);
+            assert_eq!(lhs == &rhs, expected);
+            assert_eq!(&lhs == &rhs, expected);
+        }
+    }
+}
diff --git a/packages/std/src/math/int512.rs b/packages/std/src/math/int512.rs
new file mode 100644
index 000000000..f3e5cba0f
--- /dev/null
+++ b/packages/std/src/math/int512.rs
@@ -0,0 +1,1236 @@
+use forward_ref::{forward_ref_binop, forward_ref_op_assign};
+use schemars::JsonSchema;
+use serde::{de, ser, Deserialize, Deserializer, Serialize};
+use std::fmt;
+use std::ops::{
+    Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Not, Rem, RemAssign, Shl, ShlAssign, Shr,
+    ShrAssign, Sub, SubAssign,
+};
+use std::str::FromStr;
+
+use crate::errors::{DivideByZeroError, DivisionError, OverflowError, OverflowOperation, StdError};
+use crate::{forward_ref_partial_eq, Uint128, Uint256, Uint512, Uint64};
+
+/// Used internally - we don't want to leak this type since we might change
+/// the implementation in the future.
+use bnum::types::{I512, U512};
+
+/// An implementation of i512 that is using strings for JSON encoding/decoding,
+/// such that the full i512 range can be used for clients that convert JSON numbers to floats,
+/// like JavaScript and jq.
+///
+/// # Examples
+///
+/// Use `from` to create instances out of primitive uint types or `new` to provide big
+/// endian bytes:
+///
+/// ```
+/// # use cosmwasm_std::Int512;
+/// let a = Int512::from(258u128);
+/// let b = Int512::new([
+///     0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8,
+///     0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8,
+///     0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8,
+///     0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8,
+///     0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8,
+///     0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8,
+///     0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8,
+///     0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 1u8, 2u8,
+/// ]);
+/// assert_eq!(a, b);
+/// ```
+#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, JsonSchema)]
+pub struct Int512(#[schemars(with = "String")] I512);
+
+forward_ref_partial_eq!(Int512, Int512);
+
+impl Int512 {
+    pub const MAX: Int512 = Int512(I512::MAX);
+    pub const MIN: Int512 = Int512(I512::MIN);
+
+    /// Creates a Int512(value) from a big endian representation. It's just an alias for
+    /// `from_be_bytes`.
+    #[inline]
+    pub const fn new(value: [u8; 64]) -> Self {
+        Self::from_be_bytes(value)
+    }
+
+    /// Creates a Int512(0)
+    #[inline]
+    pub const fn zero() -> Self {
+        Int512(I512::ZERO)
+    }
+
+    /// Creates a Int512(1)
+    #[inline]
+    pub const fn one() -> Self {
+        Self(I512::ONE)
+    }
+
+    #[must_use]
+    pub const fn from_be_bytes(data: [u8; 64]) -> Self {
+        let words: [u64; 8] = [
+            u64::from_le_bytes([
+                data[63], data[62], data[61], data[60], data[59], data[58], data[57], data[56],
+            ]),
+            u64::from_le_bytes([
+                data[55], data[54], data[53], data[52], data[51], data[50], data[49], data[48],
+            ]),
+            u64::from_le_bytes([
+                data[47], data[46], data[45], data[44], data[43], data[42], data[41], data[40],
+            ]),
+            u64::from_le_bytes([
+                data[39], data[38], data[37], data[36], data[35], data[34], data[33], data[32],
+            ]),
+            u64::from_le_bytes([
+                data[31], data[30], data[29], data[28], data[27], data[26], data[25], data[24],
+            ]),
+            u64::from_le_bytes([
+                data[23], data[22], data[21], data[20], data[19], data[18], data[17], data[16],
+            ]),
+            u64::from_le_bytes([
+                data[15], data[14], data[13], data[12], data[11], data[10], data[9], data[8],
+            ]),
+            u64::from_le_bytes([
+                data[7], data[6], data[5], data[4], data[3], data[2], data[1], data[0],
+            ]),
+        ];
+        Self(I512::from_bits(U512::from_digits(words)))
+    }
+
+    #[must_use]
+    pub const fn from_le_bytes(data: [u8; 64]) -> Self {
+        let words: [u64; 8] = [
+            u64::from_le_bytes([
+                data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
+            ]),
+            u64::from_le_bytes([
+                data[8], data[9], data[10], data[11], data[12], data[13], data[14], data[15],
+            ]),
+            u64::from_le_bytes([
+                data[16], data[17], data[18], data[19], data[20], data[21], data[22], data[23],
+            ]),
+            u64::from_le_bytes([
+                data[24], data[25], data[26], data[27], data[28], data[29], data[30], data[31],
+            ]),
+            u64::from_le_bytes([
+                data[32], data[33], data[34], data[35], data[36], data[37], data[38], data[39],
+            ]),
+            u64::from_le_bytes([
+                data[40], data[41], data[42], data[43], data[44], data[45], data[46], data[47],
+            ]),
+            u64::from_le_bytes([
+                data[48], data[49], data[50], data[51], data[52], data[53], data[54], data[55],
+            ]),
+            u64::from_le_bytes([
+                data[56], data[57], data[58], data[59], data[60], data[61], data[62], data[63],
+            ]),
+        ];
+        Self(I512::from_bits(U512::from_digits(words)))
+    }
+
+    /// Returns a copy of the number as big endian bytes.
+    #[must_use = "this returns the result of the operation, without modifying the original"]
+    pub const fn to_be_bytes(self) -> [u8; 64] {
+        let bits = self.0.to_bits();
+        let words = bits.digits();
+        let words = [
+            words[7].to_be_bytes(),
+            words[6].to_be_bytes(),
+            words[5].to_be_bytes(),
+            words[4].to_be_bytes(),
+            words[3].to_be_bytes(),
+            words[2].to_be_bytes(),
+            words[1].to_be_bytes(),
+            words[0].to_be_bytes(),
+        ];
+        unsafe { std::mem::transmute::<[[u8; 8]; 8], [u8; 64]>(words) }
+    }
+
+    /// Returns a copy of the number as little endian bytes.
+    #[must_use = "this returns the result of the operation, without modifying the original"]
+    pub const fn to_le_bytes(self) -> [u8; 64] {
+        let bits = self.0.to_bits();
+        let words = bits.digits();
+        let words = [
+            words[0].to_le_bytes(),
+            words[1].to_le_bytes(),
+            words[2].to_le_bytes(),
+            words[3].to_le_bytes(),
+            words[4].to_le_bytes(),
+            words[5].to_le_bytes(),
+            words[6].to_le_bytes(),
+            words[7].to_le_bytes(),
+        ];
+        unsafe { std::mem::transmute::<[[u8; 8]; 8], [u8; 64]>(words) }
+    }
+
+    #[must_use]
+    pub const fn is_zero(&self) -> bool {
+        self.0.is_zero()
+    }
+
+    #[must_use = "this returns the result of the operation, without modifying the original"]
+    pub fn pow(self, exp: u32) -> Self {
+        Self(self.0.pow(exp))
+    }
+
+    pub fn checked_add(self, other: Self) -> Result<Self, OverflowError> {
+        self.0
+            .checked_add(other.0)
+            .map(Self)
+            .ok_or_else(|| OverflowError::new(OverflowOperation::Add, self, other))
+    }
+
+    pub fn checked_sub(self, other: Self) -> Result<Self, OverflowError> {
+        self.0
+            .checked_sub(other.0)
+            .map(Self)
+            .ok_or_else(|| OverflowError::new(OverflowOperation::Sub, self, other))
+    }
+
+    pub fn checked_mul(self, other: Self) -> Result<Self, OverflowError> {
+        self.0
+            .checked_mul(other.0)
+            .map(Self)
+            .ok_or_else(|| OverflowError::new(OverflowOperation::Mul, self, other))
+    }
+
+    pub fn checked_pow(self, exp: u32) -> Result<Self, OverflowError> {
+        self.0
+            .checked_pow(exp)
+            .map(Self)
+            .ok_or_else(|| OverflowError::new(OverflowOperation::Pow, self, exp))
+    }
+
+    pub fn checked_div(self, other: Self) -> Result<Self, DivisionError> {
+        if other.is_zero() {
+            return Err(DivisionError::DivideByZero);
+        }
+        self.0
+            .checked_div(other.0)
+            .map(Self)
+            .ok_or(DivisionError::Overflow)
+    }
+
+    pub fn checked_div_euclid(self, other: Self) -> Result<Self, DivisionError> {
+        if other.is_zero() {
+            return Err(DivisionError::DivideByZero);
+        }
+        self.0
+            .checked_div_euclid(other.0)
+            .map(Self)
+            .ok_or(DivisionError::Overflow)
+    }
+
+    pub fn checked_rem(self, other: Self) -> Result<Self, DivideByZeroError> {
+        self.0
+            .checked_rem(other.0)
+            .map(Self)
+            .ok_or_else(|| DivideByZeroError::new(self))
+    }
+
+    pub fn checked_shr(self, other: u32) -> Result<Self, OverflowError> {
+        if other >= 512 {
+            return Err(OverflowError::new(OverflowOperation::Shr, self, other));
+        }
+
+        Ok(Self(self.0.shr(other)))
+    }
+
+    pub fn checked_shl(self, other: u32) -> Result<Self, OverflowError> {
+        if other >= 512 {
+            return Err(OverflowError::new(OverflowOperation::Shl, self, other));
+        }
+
+        Ok(Self(self.0.shl(other)))
+    }
+
+    #[must_use = "this returns the result of the operation, without modifying the original"]
+    #[inline]
+    pub fn wrapping_add(self, other: Self) -> Self {
+        Self(self.0.wrapping_add(other.0))
+    }
+
+    #[must_use = "this returns the result of the operation, without modifying the original"]
+    #[inline]
+    pub fn wrapping_sub(self, other: Self) -> Self {
+        Self(self.0.wrapping_sub(other.0))
+    }
+
+    #[must_use = "this returns the result of the operation, without modifying the original"]
+    #[inline]
+    pub fn wrapping_mul(self, other: Self) -> Self {
+        Self(self.0.wrapping_mul(other.0))
+    }
+
+    #[must_use = "this returns the result of the operation, without modifying the original"]
+    #[inline]
+    pub fn wrapping_pow(self, other: u32) -> Self {
+        Self(self.0.wrapping_pow(other))
+    }
+
+    #[must_use = "this returns the result of the operation, without modifying the original"]
+    pub fn saturating_add(self, other: Self) -> Self {
+        Self(self.0.saturating_add(other.0))
+    }
+
+    #[must_use = "this returns the result of the operation, without modifying the original"]
+    pub fn saturating_sub(self, other: Self) -> Self {
+        Self(self.0.saturating_sub(other.0))
+    }
+
+    #[must_use = "this returns the result of the operation, without modifying the original"]
+    pub fn saturating_mul(self, other: Self) -> Self {
+        Self(self.0.saturating_mul(other.0))
+    }
+
+    #[must_use = "this returns the result of the operation, without modifying the original"]
+    pub fn saturating_pow(self, exp: u32) -> Self {
+        Self(self.0.saturating_pow(exp))
+    }
+
+    #[must_use = "this returns the result of the operation, without modifying the original"]
+    pub fn abs_diff(self, other: Self) -> Uint512 {
+        Uint512(self.0.abs_diff(other.0))
+    }
+}
+
+impl From<Uint256> for Int512 {
+    fn from(val: Uint256) -> Self {
+        let mut bytes = [0u8; 64];
+        bytes[32..].copy_from_slice(&val.to_be_bytes());
+
+        Self::from_be_bytes(bytes)
+    }
+}
+
+impl From<Uint128> for Int512 {
+    fn from(val: Uint128) -> Self {
+        val.u128().into()
+    }
+}
+
+impl From<Uint64> for Int512 {
+    fn from(val: Uint64) -> Self {
+        val.u64().into()
+    }
+}
+
+impl From<u128> for Int512 {
+    fn from(val: u128) -> Self {
+        Int512(val.into())
+    }
+}
+
+impl From<u64> for Int512 {
+    fn from(val: u64) -> Self {
+        Int512(val.into())
+    }
+}
+
+impl From<u32> for Int512 {
+    fn from(val: u32) -> Self {
+        Int512(val.into())
+    }
+}
+
+impl From<u16> for Int512 {
+    fn from(val: u16) -> Self {
+        Int512(val.into())
+    }
+}
+
+impl From<u8> for Int512 {
+    fn from(val: u8) -> Self {
+        Int512(val.into())
+    }
+}
+
+impl From<i128> for Int512 {
+    fn from(val: i128) -> Self {
+        Int512(val.into())
+    }
+}
+
+impl From<i64> for Int512 {
+    fn from(val: i64) -> Self {
+        Int512(val.into())
+    }
+}
+
+impl From<i32> for Int512 {
+    fn from(val: i32) -> Self {
+        Int512(val.into())
+    }
+}
+
+impl From<i16> for Int512 {
+    fn from(val: i16) -> Self {
+        Int512(val.into())
+    }
+}
+
+impl From<i8> for Int512 {
+    fn from(val: i8) -> Self {
+        Int512(val.into())
+    }
+}
+
+impl TryFrom<&str> for Int512 {
+    type Error = StdError;
+
+    fn try_from(val: &str) -> Result<Self, Self::Error> {
+        Self::from_str(val)
+    }
+}
+
+impl FromStr for Int512 {
+    type Err = StdError;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        match I512::from_str_radix(s, 10) {
+            Ok(u) => Ok(Self(u)),
+            Err(e) => Err(StdError::generic_err(format!("Parsing Int512: {e}"))),
+        }
+    }
+}
+
+impl From<Int512> for String {
+    fn from(original: Int512) -> Self {
+        original.to_string()
+    }
+}
+
+impl fmt::Display for Int512 {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        // The inner type doesn't work as expected with padding, so we
+        // work around that. Remove this code when the upstream padding is fixed.
+        let unpadded = self.0.to_string();
+        let numeric = unpadded.strip_prefix('-').unwrap_or(&unpadded);
+
+        f.pad_integral(self >= &Self::zero(), "", numeric)
+    }
+}
+
+impl Add<Int512> for Int512 {
+    type Output = Self;
+
+    fn add(self, rhs: Self) -> Self {
+        Int512(self.0.checked_add(rhs.0).unwrap())
+    }
+}
+forward_ref_binop!(impl Add, add for Int512, Int512);
+
+impl Sub<Int512> for Int512 {
+    type Output = Self;
+
+    fn sub(self, rhs: Self) -> Self {
+        Int512(self.0.checked_sub(rhs.0).unwrap())
+    }
+}
+forward_ref_binop!(impl Sub, sub for Int512, Int512);
+
+impl SubAssign<Int512> for Int512 {
+    fn sub_assign(&mut self, rhs: Int512) {
+        self.0 = self.0.checked_sub(rhs.0).unwrap();
+    }
+}
+forward_ref_op_assign!(impl SubAssign, sub_assign for Int512, Int512);
+
+impl Div<Int512> for Int512 {
+    type Output = Self;
+
+    fn div(self, rhs: Self) -> Self::Output {
+        Self(self.0.checked_div(rhs.0).unwrap())
+    }
+}
+forward_ref_binop!(impl Div, div for Int512, Int512);
+
+impl Rem for Int512 {
+    type Output = Self;
+
+    /// # Panics
+    ///
+    /// This operation will panic if `rhs` is zero.
+    #[inline]
+    fn rem(self, rhs: Self) -> Self {
+        Self(self.0.rem(rhs.0))
+    }
+}
+forward_ref_binop!(impl Rem, rem for Int512, Int512);
+
+impl Not for Int512 {
+    type Output = Self;
+
+    fn not(self) -> Self::Output {
+        Self(!self.0)
+    }
+}
+
+impl Neg for Int512 {
+    type Output = Self;
+
+    fn neg(self) -> Self::Output {
+        Self(-self.0)
+    }
+}
+
+impl RemAssign<Int512> for Int512 {
+    fn rem_assign(&mut self, rhs: Int512) {
+        *self = *self % rhs;
+    }
+}
+forward_ref_op_assign!(impl RemAssign, rem_assign for Int512, Int512);
+
+impl Mul<Int512> for Int512 {
+    type Output = Self;
+
+    fn mul(self, rhs: Self) -> Self::Output {
+        Self(self.0.checked_mul(rhs.0).unwrap())
+    }
+}
+forward_ref_binop!(impl Mul, mul for Int512, Int512);
+
+impl MulAssign<Int512> for Int512 {
+    fn mul_assign(&mut self, rhs: Self) {
+        self.0 = self.0.checked_mul(rhs.0).unwrap();
+    }
+}
+forward_ref_op_assign!(impl MulAssign, mul_assign for Int512, Int512);
+
+impl Shr<u32> for Int512 {
+    type Output = Self;
+
+    fn shr(self, rhs: u32) -> Self::Output {
+        self.checked_shr(rhs).unwrap_or_else(|_| {
+            panic!("right shift error: {rhs} is larger or equal than the number of bits in Int512",)
+        })
+    }
+}
+forward_ref_binop!(impl Shr, shr for Int512, u32);
+
+impl Shl<u32> for Int512 {
+    type Output = Self;
+
+    fn shl(self, rhs: u32) -> Self::Output {
+        self.checked_shl(rhs).unwrap_or_else(|_| {
+            panic!("left shift error: {rhs} is larger or equal than the number of bits in Int512",)
+        })
+    }
+}
+forward_ref_binop!(impl Shl, shl for Int512, u32);
+
+impl AddAssign<Int512> for Int512 {
+    fn add_assign(&mut self, rhs: Int512) {
+        self.0 = self.0.checked_add(rhs.0).unwrap();
+    }
+}
+forward_ref_op_assign!(impl AddAssign, add_assign for Int512, Int512);
+
+impl DivAssign<Int512> for Int512 {
+    fn div_assign(&mut self, rhs: Self) {
+        self.0 = self.0.checked_div(rhs.0).unwrap();
+    }
+}
+forward_ref_op_assign!(impl DivAssign, div_assign for Int512, Int512);
+
+impl ShrAssign<u32> for Int512 {
+    fn shr_assign(&mut self, rhs: u32) {
+        *self = Shr::<u32>::shr(*self, rhs);
+    }
+}
+forward_ref_op_assign!(impl ShrAssign, shr_assign for Int512, u32);
+
+impl ShlAssign<u32> for Int512 {
+    fn shl_assign(&mut self, rhs: u32) {
+        *self = Shl::<u32>::shl(*self, rhs);
+    }
+}
+forward_ref_op_assign!(impl ShlAssign, shl_assign for Int512, u32);
+
+impl Serialize for Int512 {
+    /// Serializes as an integer string using base 10
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: ser::Serializer,
+    {
+        serializer.serialize_str(&self.to_string())
+    }
+}
+
+impl<'de> Deserialize<'de> for Int512 {
+    /// Deserialized from an integer string using base 10
+    fn deserialize<D>(deserializer: D) -> Result<Int512, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        deserializer.deserialize_str(Int512Visitor)
+    }
+}
+
+struct Int512Visitor;
+
+impl<'de> de::Visitor<'de> for Int512Visitor {
+    type Value = Int512;
+
+    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+        formatter.write_str("string-encoded integer")
+    }
+
+    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
+    where
+        E: de::Error,
+    {
+        Int512::try_from(v).map_err(|e| E::custom(format!("invalid Int512 '{v}' - {e}")))
+    }
+}
+
+impl<A> std::iter::Sum<A> for Int512
+where
+    Self: Add<A, Output = Self>,
+{
+    fn sum<I: Iterator<Item = A>>(iter: I) -> Self {
+        iter.fold(Self::zero(), Add::add)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::{from_slice, to_vec};
+
+    #[test]
+    fn size_of_works() {
+        assert_eq!(std::mem::size_of::<Int512>(), 64);
+    }
+
+    #[test]
+    fn int512_new_works() {
+        let num = Int512::new([1; 64]);
+        let a: [u8; 64] = num.to_be_bytes();
+        assert_eq!(a, [1; 64]);
+
+        let be_bytes = [
+            0u8, 222u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8,
+            0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8,
+            0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8,
+            0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 1u8, 2u8, 3u8,
+        ];
+        let num = Int512::new(be_bytes);
+        let resulting_bytes: [u8; 64] = num.to_be_bytes();
+        assert_eq!(be_bytes, resulting_bytes);
+    }
+
+    #[test]
+    fn int512_zero_works() {
+        let zero = Int512::zero();
+        assert_eq!(zero.to_be_bytes(), [0; 64]);
+    }
+
+    #[test]
+    fn uint512_one_works() {
+        let one = Int512::one();
+        let mut one_be = [0; 64];
+        one_be[63] = 1;
+
+        assert_eq!(one.to_be_bytes(), one_be);
+    }
+
+    #[test]
+    fn int512_endianness() {
+        let be_bytes = [
+            0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8,
+            0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8,
+            0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8,
+            0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 1u8, 2u8, 3u8,
+        ];
+        let le_bytes = [
+            3u8, 2u8, 1u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8,
+            0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8,
+            0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8,
+            0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8,
+        ];
+
+        // These should all be the same.
+        let num1 = Int512::new(be_bytes);
+        let num2 = Int512::from_be_bytes(be_bytes);
+        let num3 = Int512::from_le_bytes(le_bytes);
+        assert_eq!(num1, Int512::from(65536u32 + 512 + 3));
+        assert_eq!(num1, num2);
+        assert_eq!(num1, num3);
+    }
+
+    #[test]
+    fn int512_convert_from() {
+        let a = Int512::from(5u128);
+        assert_eq!(a.0, I512::from(5u32));
+
+        let a = Int512::from(5u64);
+        assert_eq!(a.0, I512::from(5u32));
+
+        let a = Int512::from(5u32);
+        assert_eq!(a.0, I512::from(5u32));
+
+        let a = Int512::from(5u16);
+        assert_eq!(a.0, I512::from(5u32));
+
+        let a = Int512::from(5u8);
+        assert_eq!(a.0, I512::from(5u32));
+
+        let a = Int512::from(-5i128);
+        assert_eq!(a.0, I512::from(-5i32));
+
+        let a = Int512::from(-5i64);
+        assert_eq!(a.0, I512::from(-5i32));
+
+        let a = Int512::from(-5i32);
+        assert_eq!(a.0, I512::from(-5i32));
+
+        let a = Int512::from(-5i16);
+        assert_eq!(a.0, I512::from(-5i32));
+
+        let a = Int512::from(-5i8);
+        assert_eq!(a.0, I512::from(-5i32));
+
+        let result = Int512::try_from("34567");
+        assert_eq!(
+            result.unwrap().0,
+            I512::from_str_radix("34567", 10).unwrap()
+        );
+
+        let result = Int512::try_from("1.23");
+        assert!(result.is_err());
+    }
+
+    #[test]
+    fn int512_implements_display() {
+        let a = Int512::from(12345u32);
+        assert_eq!(format!("Embedded: {a}"), "Embedded: 12345");
+        assert_eq!(a.to_string(), "12345");
+
+        let a = Int512::from(-12345i32);
+        assert_eq!(format!("Embedded: {a}"), "Embedded: -12345");
+        assert_eq!(a.to_string(), "-12345");
+
+        let a = Int512::zero();
+        assert_eq!(format!("Embedded: {a}"), "Embedded: 0");
+        assert_eq!(a.to_string(), "0");
+    }
+
+    #[test]
+    fn int512_display_padding_works() {
+        let a = Int512::from(123u64);
+        assert_eq!(format!("Embedded: {a:05}"), "Embedded: 00123");
+
+        let a = Int512::from(-123i64);
+        assert_eq!(format!("Embedded: {a:05}"), "Embedded: -0123");
+    }
+
+    #[test]
+    fn int512_to_be_bytes_works() {
+        assert_eq!(Int512::zero().to_be_bytes(), [0; 64]);
+
+        let mut max = [0xff; 64];
+        max[0] = 0x7f;
+        assert_eq!(Int512::MAX.to_be_bytes(), max);
+
+        let mut one = [0; 64];
+        one[63] = 1;
+        assert_eq!(Int512::from(1u128).to_be_bytes(), one);
+        // Python: `[b for b in (240282366920938463463374607431768124608).to_bytes(64, "big")]`
+        assert_eq!(
+            Int512::from(240282366920938463463374607431768124608u128).to_be_bytes(),
+            [
+                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 180, 196, 179, 87, 165,
+                121, 59, 133, 246, 117, 221, 191, 255, 254, 172, 192
+            ]
+        );
+        assert_eq!(
+            Int512::from_be_bytes([
+                17, 4, 23, 32, 87, 67, 123, 200, 58, 91, 0, 38, 33, 21, 67, 78, 87, 76, 65, 54,
+                211, 201, 192, 7, 42, 233, 2, 240, 200, 115, 150, 240, 218, 88, 106, 45, 208, 134,
+                238, 119, 85, 22, 14, 88, 166, 195, 154, 73, 64, 10, 44, 59, 13, 22, 47, 12, 99, 8,
+                252, 96, 230, 187, 38, 29
+            ])
+            .to_be_bytes(),
+            [
+                17, 4, 23, 32, 87, 67, 123, 200, 58, 91, 0, 38, 33, 21, 67, 78, 87, 76, 65, 54,
+                211, 201, 192, 7, 42, 233, 2, 240, 200, 115, 150, 240, 218, 88, 106, 45, 208, 134,
+                238, 119, 85, 22, 14, 88, 166, 195, 154, 73, 64, 10, 44, 59, 13, 22, 47, 12, 99, 8,
+                252, 96, 230, 187, 38, 29
+            ]
+        );
+    }
+
+    #[test]
+    fn int512_to_le_bytes_works() {
+        assert_eq!(Int512::zero().to_le_bytes(), [0; 64]);
+
+        let mut max = [0xff; 64];
+        max[63] = 0x7f;
+        assert_eq!(Int512::MAX.to_le_bytes(), max);
+
+        let mut one = [0; 64];
+        one[0] = 1;
+        assert_eq!(Int512::from(1u128).to_le_bytes(), one);
+        // Python: `[b for b in (240282366920938463463374607431768124608).to_bytes(64, "little")]`
+        assert_eq!(
+            Int512::from(240282366920938463463374607431768124608u128).to_le_bytes(),
+            [
+                192, 172, 254, 255, 191, 221, 117, 246, 133, 59, 121, 165, 87, 179, 196, 180, 0, 0,
+                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+            ]
+        );
+        assert_eq!(
+            Int512::from_be_bytes([
+                17, 4, 23, 32, 87, 67, 123, 200, 58, 91, 0, 38, 33, 21, 67, 78, 87, 76, 65, 54,
+                211, 201, 192, 7, 42, 233, 2, 240, 200, 115, 150, 240, 218, 88, 106, 45, 208, 134,
+                238, 119, 85, 22, 14, 88, 166, 195, 154, 73, 64, 10, 44, 59, 13, 22, 47, 12, 99, 8,
+                252, 96, 230, 187, 38, 29
+            ])
+            .to_le_bytes(),
+            [
+                29, 38, 187, 230, 96, 252, 8, 99, 12, 47, 22, 13, 59, 44, 10, 64, 73, 154, 195,
+                166, 88, 14, 22, 85, 119, 238, 134, 208, 45, 106, 88, 218, 240, 150, 115, 200, 240,
+                2, 233, 42, 7, 192, 201, 211, 54, 65, 76, 87, 78, 67, 21, 33, 38, 0, 91, 58, 200,
+                123, 67, 87, 32, 23, 4, 17
+            ]
+        );
+    }
+
+    #[test]
+    fn int512_is_zero_works() {
+        assert!(Int512::zero().is_zero());
+        assert!(Int512(I512::from(0u32)).is_zero());
+
+        assert!(!Int512::from(1u32).is_zero());
+        assert!(!Int512::from(123u32).is_zero());
+        assert!(!Int512::from(-123i32).is_zero());
+    }
+
+    #[test]
+    fn int512_wrapping_methods() {
+        // wrapping_add
+        assert_eq!(
+            Int512::from(2u32).wrapping_add(Int512::from(2u32)),
+            Int512::from(4u32)
+        ); // non-wrapping
+        assert_eq!(Int512::MAX.wrapping_add(Int512::from(1u32)), Int512::MIN); // wrapping
+
+        // wrapping_sub
+        assert_eq!(
+            Int512::from(7u32).wrapping_sub(Int512::from(5u32)),
+            Int512::from(2u32)
+        ); // non-wrapping
+        assert_eq!(Int512::MIN.wrapping_sub(Int512::from(1u32)), Int512::MAX); // wrapping
+
+        // wrapping_mul
+        assert_eq!(
+            Int512::from(3u32).wrapping_mul(Int512::from(2u32)),
+            Int512::from(6u32)
+        ); // non-wrapping
+        assert_eq!(
+            Int512::MAX.wrapping_mul(Int512::from(2u32)),
+            Int512::from(-2i32)
+        ); // wrapping
+
+        // wrapping_pow
+        assert_eq!(Int512::from(2u32).wrapping_pow(3), Int512::from(8u32)); // non-wrapping
+        assert_eq!(Int512::MAX.wrapping_pow(2), Int512::from(1u32)); // wrapping
+    }
+
+    #[test]
+    fn int512_json() {
+        let orig = Int512::from(1234567890987654321u128);
+        let serialized = to_vec(&orig).unwrap();
+        assert_eq!(serialized.as_slice(), b"\"1234567890987654321\"");
+        let parsed: Int512 = from_slice(&serialized).unwrap();
+        assert_eq!(parsed, orig);
+    }
+
+    #[test]
+    fn int512_compare() {
+        let a = Int512::from(12345u32);
+        let b = Int512::from(23456u32);
+
+        assert!(a < b);
+        assert!(b > a);
+        assert_eq!(a, Int512::from(12345u32));
+    }
+
+    #[test]
+    #[allow(clippy::op_ref)]
+    fn int512_math() {
+        let a = Int512::from(-12345i32);
+        let b = Int512::from(23456u32);
+
+        // test + with owned and reference right hand side
+        assert_eq!(a + b, Int512::from(11111u32));
+        assert_eq!(a + &b, Int512::from(11111u32));
+
+        // test - with owned and reference right hand side
+        assert_eq!(b - a, Int512::from(35801u32));
+        assert_eq!(b - &a, Int512::from(35801u32));
+
+        // test += with owned and reference right hand side
+        let mut c = Int512::from(300000u32);
+        c += b;
+        assert_eq!(c, Int512::from(323456u32));
+        let mut d = Int512::from(300000u32);
+        d += &b;
+        assert_eq!(d, Int512::from(323456u32));
+
+        // test -= with owned and reference right hand side
+        let mut c = Int512::from(300000u32);
+        c -= b;
+        assert_eq!(c, Int512::from(276544u32));
+        let mut d = Int512::from(300000u32);
+        d -= &b;
+        assert_eq!(d, Int512::from(276544u32));
+
+        // test - with negative result
+        assert_eq!(a - b, Int512::from(-35801i32));
+    }
+
+    #[test]
+    #[should_panic]
+    fn int512_add_overflow_panics() {
+        let _ = Int512::MAX + Int512::from(12u32);
+    }
+
+    #[test]
+    #[allow(clippy::op_ref)]
+    fn int512_sub_works() {
+        assert_eq!(Int512::from(2u32) - Int512::from(1u32), Int512::from(1u32));
+        assert_eq!(Int512::from(2u32) - Int512::from(0u32), Int512::from(2u32));
+        assert_eq!(Int512::from(2u32) - Int512::from(2u32), Int512::from(0u32));
+        assert_eq!(Int512::from(2u32) - Int512::from(3u32), Int512::from(-1i32));
+
+        // works for refs
+        let a = Int512::from(10u32);
+        let b = Int512::from(3u32);
+        let expected = Int512::from(7u32);
+        assert_eq!(a - b, expected);
+        assert_eq!(a - &b, expected);
+        assert_eq!(&a - b, expected);
+        assert_eq!(&a - &b, expected);
+    }
+
+    #[test]
+    #[should_panic]
+    fn int512_sub_overflow_panics() {
+        let _ = Int512::MIN + Int512::one() - Int512::from(2u32);
+    }
+
+    #[test]
+    fn int512_sub_assign_works() {
+        let mut a = Int512::from(14u32);
+        a -= Int512::from(2u32);
+        assert_eq!(a, Int512::from(12u32));
+
+        // works for refs
+        let mut a = Int512::from(10u32);
+        let b = Int512::from(3u32);
+        let expected = Int512::from(7u32);
+        a -= &b;
+        assert_eq!(a, expected);
+    }
+
+    #[test]
+    #[allow(clippy::op_ref)]
+    fn int512_mul_works() {
+        assert_eq!(Int512::from(2u32) * Int512::from(3u32), Int512::from(6u32));
+        assert_eq!(Int512::from(2u32) * Int512::zero(), Int512::zero());
+
+        // works for refs
+        let a = Int512::from(11u32);
+        let b = Int512::from(3u32);
+        let expected = Int512::from(33u32);
+        assert_eq!(a * b, expected);
+        assert_eq!(a * &b, expected);
+        assert_eq!(&a * b, expected);
+        assert_eq!(&a * &b, expected);
+    }
+
+    #[test]
+    fn int512_mul_assign_works() {
+        let mut a = Int512::from(14u32);
+        a *= Int512::from(2u32);
+        assert_eq!(a, Int512::from(28u32));
+
+        // works for refs
+        let mut a = Int512::from(10u32);
+        let b = Int512::from(3u32);
+        a *= &b;
+        assert_eq!(a, Int512::from(30u32));
+    }
+
+    #[test]
+    fn int512_pow_works() {
+        assert_eq!(Int512::from(2u32).pow(2), Int512::from(4u32));
+        assert_eq!(Int512::from(2u32).pow(10), Int512::from(1024u32));
+    }
+
+    #[test]
+    #[should_panic]
+    fn int512_pow_overflow_panics() {
+        _ = Int512::MAX.pow(2u32);
+    }
+
+    #[test]
+    fn int512_shr_works() {
+        let original = Int512::new([
+            0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8,
+            0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8,
+            0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8,
+            0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 2u8, 0u8, 4u8, 2u8,
+        ]);
+
+        let shifted = Int512::new([
+            0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8,
+            0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8,
+            0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8,
+            0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 128u8, 1u8, 0u8,
+        ]);
+
+        assert_eq!(original >> 2u32, shifted);
+    }
+
+    #[test]
+    #[should_panic]
+    fn int512_shr_overflow_panics() {
+        let _ = Int512::from(1u32) >> 512u32;
+    }
+
+    #[test]
+    fn sum_works() {
+        let nums = vec![
+            Int512::from(17u32),
+            Int512::from(123u32),
+            Int512::from(540u32),
+            Int512::from(82u32),
+        ];
+        let expected = Int512::from(762u32);
+
+        let sum_as_ref: Int512 = nums.iter().sum();
+        assert_eq!(expected, sum_as_ref);
+
+        let sum_as_owned: Int512 = nums.into_iter().sum();
+        assert_eq!(expected, sum_as_owned);
+    }
+
+    #[test]
+    fn int512_methods() {
+        // checked_*
+        assert!(matches!(
+            Int512::MAX.checked_add(Int512::from(1u32)),
+            Err(OverflowError { .. })
+        ));
+        assert_eq!(
+            Int512::from(1u32).checked_add(Int512::from(1u32)),
+            Ok(Int512::from(2u32)),
+        );
+        assert!(matches!(
+            Int512::MIN.checked_sub(Int512::from(1u32)),
+            Err(OverflowError { .. })
+        ));
+        assert_eq!(
+            Int512::from(2u32).checked_sub(Int512::from(1u32)),
+            Ok(Int512::from(1u32)),
+        );
+        assert!(matches!(
+            Int512::MAX.checked_mul(Int512::from(2u32)),
+            Err(OverflowError { .. })
+        ));
+        assert_eq!(
+            Int512::from(2u32).checked_mul(Int512::from(2u32)),
+            Ok(Int512::from(4u32)),
+        );
+        assert!(matches!(
+            Int512::MAX.checked_pow(2u32),
+            Err(OverflowError { .. })
+        ));
+        assert_eq!(Int512::from(2u32).checked_pow(3u32), Ok(Int512::from(8u32)),);
+        assert_eq!(
+            Int512::MAX.checked_div(Int512::from(0u32)),
+            Err(DivisionError::DivideByZero)
+        );
+        assert_eq!(
+            Int512::from(6u32).checked_div(Int512::from(2u32)),
+            Ok(Int512::from(3u32)),
+        );
+        assert_eq!(
+            Int512::MAX.checked_div_euclid(Int512::from(0u32)),
+            Err(DivisionError::DivideByZero)
+        );
+        assert_eq!(
+            Int512::from(6u32).checked_div_euclid(Int512::from(2u32)),
+            Ok(Int512::from(3u32)),
+        );
+        assert_eq!(
+            Int512::from(7u32).checked_div_euclid(Int512::from(2u32)),
+            Ok(Int512::from(3u32)),
+        );
+        assert!(matches!(
+            Int512::MAX.checked_rem(Int512::from(0u32)),
+            Err(DivideByZeroError { .. })
+        ));
+        // checked_* with negative numbers
+        assert_eq!(
+            Int512::from(-12i32).checked_div(Int512::from(10i32)),
+            Ok(Int512::from(-1i32)),
+        );
+        assert_eq!(
+            Int512::from(-2i32).checked_pow(3u32),
+            Ok(Int512::from(-8i32)),
+        );
+        assert_eq!(
+            Int512::from(-6i32).checked_mul(Int512::from(-7i32)),
+            Ok(Int512::from(42i32)),
+        );
+        assert_eq!(
+            Int512::from(-2i32).checked_add(Int512::from(3i32)),
+            Ok(Int512::from(1i32)),
+        );
+        assert_eq!(
+            Int512::from(-1i32).checked_div_euclid(Int512::from(-2i32)),
+            Ok(Int512::from(1u32)),
+        );
+
+        // saturating_*
+        assert_eq!(Int512::MAX.saturating_add(Int512::from(1u32)), Int512::MAX);
+        assert_eq!(Int512::MIN.saturating_sub(Int512::from(1u32)), Int512::MIN);
+        assert_eq!(Int512::MAX.saturating_mul(Int512::from(2u32)), Int512::MAX);
+        assert_eq!(Int512::from(4u32).saturating_pow(2u32), Int512::from(16u32));
+        assert_eq!(Int512::MAX.saturating_pow(2u32), Int512::MAX);
+    }
+
+    #[test]
+    #[allow(clippy::op_ref)]
+    fn int512_implements_rem() {
+        let a = Int512::from(10u32);
+        assert_eq!(a % Int512::from(10u32), Int512::zero());
+        assert_eq!(a % Int512::from(2u32), Int512::zero());
+        assert_eq!(a % Int512::from(1u32), Int512::zero());
+        assert_eq!(a % Int512::from(3u32), Int512::from(1u32));
+        assert_eq!(a % Int512::from(4u32), Int512::from(2u32));
+
+        assert_eq!(
+            Int512::from(-12i32) % Int512::from(10i32),
+            Int512::from(-2i32)
+        );
+        assert_eq!(
+            Int512::from(12i32) % Int512::from(-10i32),
+            Int512::from(2i32)
+        );
+        assert_eq!(
+            Int512::from(-12i32) % Int512::from(-10i32),
+            Int512::from(-2i32)
+        );
+
+        // works for refs
+        let a = Int512::from(10u32);
+        let b = Int512::from(3u32);
+        let expected = Int512::from(1u32);
+        assert_eq!(a % b, expected);
+        assert_eq!(a % &b, expected);
+        assert_eq!(&a % b, expected);
+        assert_eq!(&a % &b, expected);
+    }
+
+    #[test]
+    #[should_panic(expected = "divisor of zero")]
+    fn int512_rem_panics_for_zero() {
+        let _ = Int512::from(10u32) % Int512::zero();
+    }
+
+    #[test]
+    fn int512_rem_assign_works() {
+        let mut a = Int512::from(30u32);
+        a %= Int512::from(4u32);
+        assert_eq!(a, Int512::from(2u32));
+
+        // works for refs
+        let mut a = Int512::from(25u32);
+        let b = Int512::from(6u32);
+        a %= &b;
+        assert_eq!(a, Int512::from(1u32));
+    }
+
+    #[test]
+    fn int512_shr() {
+        let x: Int512 = 0x8000_0000_0000_0000_0000_0000_0000_0000u128.into();
+        assert_eq!(x >> 0, x); // right shift by 0 should be no-op
+        assert_eq!(
+            x >> 1,
+            Int512::from(0x4000_0000_0000_0000_0000_0000_0000_0000u128)
+        );
+        assert_eq!(
+            x >> 4,
+            Int512::from(0x0800_0000_0000_0000_0000_0000_0000_0000u128)
+        );
+        // right shift of MIN value by the maximum shift value should result in -1 (filled with 1s)
+        assert_eq!(
+            Int512::MIN >> (std::mem::size_of::<Int512>() as u32 * 8 - 1),
+            -Int512::one()
+        );
+    }
+
+    #[test]
+    fn int512_shl() {
+        let x: Int512 = 0x0800_0000_0000_0000_0000_0000_0000_0000u128.into();
+        assert_eq!(x << 0, x); // left shift by 0 should be no-op
+        assert_eq!(
+            x << 1,
+            Int512::from(0x1000_0000_0000_0000_0000_0000_0000_0000u128)
+        );
+        assert_eq!(
+            x << 4,
+            Int512::from(0x8000_0000_0000_0000_0000_0000_0000_0000u128)
+        );
+        // left shift by by the maximum shift value should result in MIN
+        assert_eq!(
+            Int512::one() << (std::mem::size_of::<Int512>() as u32 * 8 - 1),
+            Int512::MIN
+        );
+    }
+
+    #[test]
+    fn int512_abs_diff_works() {
+        let a = Int512::from(42u32);
+        let b = Int512::from(5u32);
+        let expected = Uint512::from(37u32);
+        assert_eq!(a.abs_diff(b), expected);
+        assert_eq!(b.abs_diff(a), expected);
+
+        let c = Int512::from(-5i32);
+        assert_eq!(b.abs_diff(c), Uint512::from(10u32));
+        assert_eq!(c.abs_diff(b), Uint512::from(10u32));
+    }
+
+    #[test]
+    #[should_panic = "attempt to negate with overflow"]
+    fn int512_neg_min_panics() {
+        _ = -Int512::MIN;
+    }
+
+    #[test]
+    fn int512_partial_eq() {
+        let test_cases = [(1, 1, true), (42, 42, true), (42, 24, false), (0, 0, true)]
+            .into_iter()
+            .map(|(lhs, rhs, expected): (u64, u64, bool)| {
+                (Int512::from(lhs), Int512::from(rhs), expected)
+            });
+
+        #[allow(clippy::op_ref)]
+        for (lhs, rhs, expected) in test_cases {
+            assert_eq!(lhs == rhs, expected);
+            assert_eq!(&lhs == rhs, expected);
+            assert_eq!(lhs == &rhs, expected);
+            assert_eq!(&lhs == &rhs, expected);
+        }
+    }
+}
diff --git a/packages/std/src/math/int64.rs b/packages/std/src/math/int64.rs
new file mode 100644
index 000000000..f26e79fe7
--- /dev/null
+++ b/packages/std/src/math/int64.rs
@@ -0,0 +1,1041 @@
+use forward_ref::{forward_ref_binop, forward_ref_op_assign};
+use schemars::JsonSchema;
+use serde::{de, ser, Deserialize, Deserializer, Serialize};
+use std::fmt;
+use std::ops::{
+    Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Not, Rem, RemAssign, Shl, ShlAssign, Shr,
+    ShrAssign, Sub, SubAssign,
+};
+use std::str::FromStr;
+
+use crate::errors::{DivideByZeroError, DivisionError, OverflowError, OverflowOperation, StdError};
+use crate::{forward_ref_partial_eq, Uint64};
+
+/// An implementation of i64 that is using strings for JSON encoding/decoding,
+/// such that the full i64 range can be used for clients that convert JSON numbers to floats,
+/// like JavaScript and jq.
+///
+/// # Examples
+///
+/// Use `from` to create instances of this and `i64` to get the value out:
+///
+/// ```
+/// # use cosmwasm_std::Int64;
+/// let a = Int64::from(258i64);
+/// assert_eq!(a.i64(), 258);
+/// ```
+#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, JsonSchema)]
+pub struct Int64(#[schemars(with = "String")] i64);
+
+forward_ref_partial_eq!(Int64, Int64);
+
+impl Int64 {
+    pub const MAX: Int64 = Int64(i64::MAX);
+    pub const MIN: Int64 = Int64(i64::MIN);
+
+    /// Creates a Int64(value).
+    ///
+    /// This method is less flexible than `from` but can be called in a const context.
+    #[inline]
+    pub const fn new(value: i64) -> Self {
+        Self(value)
+    }
+
+    /// Creates a Int64(0)
+    #[inline]
+    pub const fn zero() -> Self {
+        Int64(0)
+    }
+
+    /// Creates a Int64(1)
+    #[inline]
+    pub const fn one() -> Self {
+        Self(1)
+    }
+
+    /// Returns a copy of the internal data
+    pub const fn i64(&self) -> i64 {
+        self.0
+    }
+
+    #[must_use]
+    pub const fn from_be_bytes(data: [u8; 8]) -> Self {
+        Self(i64::from_be_bytes(data))
+    }
+
+    #[must_use]
+    pub const fn from_le_bytes(data: [u8; 8]) -> Self {
+        Self(i64::from_le_bytes(data))
+    }
+
+    /// Returns a copy of the number as big endian bytes.
+    #[must_use = "this returns the result of the operation, without modifying the original"]
+    pub const fn to_be_bytes(self) -> [u8; 8] {
+        self.0.to_be_bytes()
+    }
+
+    /// Returns a copy of the number as little endian bytes.
+    #[must_use = "this returns the result of the operation, without modifying the original"]
+    pub const fn to_le_bytes(self) -> [u8; 8] {
+        self.0.to_le_bytes()
+    }
+
+    #[must_use]
+    pub const fn is_zero(&self) -> bool {
+        self.0 == 0
+    }
+
+    #[must_use = "this returns the result of the operation, without modifying the original"]
+    pub fn pow(self, exp: u32) -> Self {
+        Self(self.0.pow(exp))
+    }
+
+    pub fn checked_add(self, other: Self) -> Result<Self, OverflowError> {
+        self.0
+            .checked_add(other.0)
+            .map(Self)
+            .ok_or_else(|| OverflowError::new(OverflowOperation::Add, self, other))
+    }
+
+    pub fn checked_sub(self, other: Self) -> Result<Self, OverflowError> {
+        self.0
+            .checked_sub(other.0)
+            .map(Self)
+            .ok_or_else(|| OverflowError::new(OverflowOperation::Sub, self, other))
+    }
+
+    pub fn checked_mul(self, other: Self) -> Result<Self, OverflowError> {
+        self.0
+            .checked_mul(other.0)
+            .map(Self)
+            .ok_or_else(|| OverflowError::new(OverflowOperation::Mul, self, other))
+    }
+
+    pub fn checked_pow(self, exp: u32) -> Result<Self, OverflowError> {
+        self.0
+            .checked_pow(exp)
+            .map(Self)
+            .ok_or_else(|| OverflowError::new(OverflowOperation::Pow, self, exp))
+    }
+
+    pub fn checked_div(self, other: Self) -> Result<Self, DivisionError> {
+        if other.is_zero() {
+            return Err(DivisionError::DivideByZero);
+        }
+        self.0
+            .checked_div(other.0)
+            .map(Self)
+            .ok_or(DivisionError::Overflow)
+    }
+
+    pub fn checked_div_euclid(self, other: Self) -> Result<Self, DivisionError> {
+        if other.is_zero() {
+            return Err(DivisionError::DivideByZero);
+        }
+        self.0
+            .checked_div_euclid(other.0)
+            .map(Self)
+            .ok_or(DivisionError::Overflow)
+    }
+
+    pub fn checked_rem(self, other: Self) -> Result<Self, DivideByZeroError> {
+        self.0
+            .checked_rem(other.0)
+            .map(Self)
+            .ok_or_else(|| DivideByZeroError::new(self))
+    }
+
+    pub fn checked_shr(self, other: u32) -> Result<Self, OverflowError> {
+        if other >= 64 {
+            return Err(OverflowError::new(OverflowOperation::Shr, self, other));
+        }
+
+        Ok(Self(self.0.shr(other)))
+    }
+
+    pub fn checked_shl(self, other: u32) -> Result<Self, OverflowError> {
+        if other >= 64 {
+            return Err(OverflowError::new(OverflowOperation::Shl, self, other));
+        }
+
+        Ok(Self(self.0.shl(other)))
+    }
+
+    #[must_use = "this returns the result of the operation, without modifying the original"]
+    #[inline]
+    pub fn wrapping_add(self, other: Self) -> Self {
+        Self(self.0.wrapping_add(other.0))
+    }
+
+    #[must_use = "this returns the result of the operation, without modifying the original"]
+    #[inline]
+    pub fn wrapping_sub(self, other: Self) -> Self {
+        Self(self.0.wrapping_sub(other.0))
+    }
+
+    #[must_use = "this returns the result of the operation, without modifying the original"]
+    #[inline]
+    pub fn wrapping_mul(self, other: Self) -> Self {
+        Self(self.0.wrapping_mul(other.0))
+    }
+
+    #[must_use = "this returns the result of the operation, without modifying the original"]
+    #[inline]
+    pub fn wrapping_pow(self, other: u32) -> Self {
+        Self(self.0.wrapping_pow(other))
+    }
+
+    #[must_use = "this returns the result of the operation, without modifying the original"]
+    pub fn saturating_add(self, other: Self) -> Self {
+        Self(self.0.saturating_add(other.0))
+    }
+
+    #[must_use = "this returns the result of the operation, without modifying the original"]
+    pub fn saturating_sub(self, other: Self) -> Self {
+        Self(self.0.saturating_sub(other.0))
+    }
+
+    #[must_use = "this returns the result of the operation, without modifying the original"]
+    pub fn saturating_mul(self, other: Self) -> Self {
+        Self(self.0.saturating_mul(other.0))
+    }
+
+    #[must_use = "this returns the result of the operation, without modifying the original"]
+    pub fn saturating_pow(self, exp: u32) -> Self {
+        Self(self.0.saturating_pow(exp))
+    }
+
+    #[must_use = "this returns the result of the operation, without modifying the original"]
+    pub fn abs_diff(self, other: Self) -> Uint64 {
+        Uint64(self.0.abs_diff(other.0))
+    }
+}
+
+impl From<u32> for Int64 {
+    fn from(val: u32) -> Self {
+        Int64(val.into())
+    }
+}
+
+impl From<u16> for Int64 {
+    fn from(val: u16) -> Self {
+        Int64(val.into())
+    }
+}
+
+impl From<u8> for Int64 {
+    fn from(val: u8) -> Self {
+        Int64(val.into())
+    }
+}
+
+impl From<i64> for Int64 {
+    fn from(val: i64) -> Self {
+        Int64(val)
+    }
+}
+
+impl From<i32> for Int64 {
+    fn from(val: i32) -> Self {
+        Int64(val.into())
+    }
+}
+
+impl From<i16> for Int64 {
+    fn from(val: i16) -> Self {
+        Int64(val.into())
+    }
+}
+
+impl From<i8> for Int64 {
+    fn from(val: i8) -> Self {
+        Int64(val.into())
+    }
+}
+
+impl TryFrom<&str> for Int64 {
+    type Error = StdError;
+
+    fn try_from(val: &str) -> Result<Self, Self::Error> {
+        Self::from_str(val)
+    }
+}
+
+impl FromStr for Int64 {
+    type Err = StdError;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        match s.parse::<i64>() {
+            Ok(u) => Ok(Self(u)),
+            Err(e) => Err(StdError::generic_err(format!("Parsing Int64: {e}"))),
+        }
+    }
+}
+
+impl From<Int64> for String {
+    fn from(original: Int64) -> Self {
+        original.to_string()
+    }
+}
+
+impl fmt::Display for Int64 {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        self.0.fmt(f)
+    }
+}
+
+impl Add<Int64> for Int64 {
+    type Output = Self;
+
+    fn add(self, rhs: Self) -> Self {
+        Int64(self.0.checked_add(rhs.0).unwrap())
+    }
+}
+forward_ref_binop!(impl Add, add for Int64, Int64);
+
+impl Sub<Int64> for Int64 {
+    type Output = Self;
+
+    fn sub(self, rhs: Self) -> Self {
+        Int64(self.0.checked_sub(rhs.0).unwrap())
+    }
+}
+forward_ref_binop!(impl Sub, sub for Int64, Int64);
+
+impl SubAssign<Int64> for Int64 {
+    fn sub_assign(&mut self, rhs: Int64) {
+        self.0 = self.0.checked_sub(rhs.0).unwrap();
+    }
+}
+forward_ref_op_assign!(impl SubAssign, sub_assign for Int64, Int64);
+
+impl Div<Int64> for Int64 {
+    type Output = Self;
+
+    fn div(self, rhs: Self) -> Self::Output {
+        Self(self.0.checked_div(rhs.0).unwrap())
+    }
+}
+forward_ref_binop!(impl Div, div for Int64, Int64);
+
+impl Rem for Int64 {
+    type Output = Self;
+
+    /// # Panics
+    ///
+    /// This operation will panic if `rhs` is zero.
+    #[inline]
+    fn rem(self, rhs: Self) -> Self {
+        Self(self.0.rem(rhs.0))
+    }
+}
+forward_ref_binop!(impl Rem, rem for Int64, Int64);
+
+impl Not for Int64 {
+    type Output = Self;
+
+    fn not(self) -> Self::Output {
+        Self(!self.0)
+    }
+}
+
+impl Neg for Int64 {
+    type Output = Self;
+
+    fn neg(self) -> Self::Output {
+        Self(-self.0)
+    }
+}
+
+impl RemAssign<Int64> for Int64 {
+    fn rem_assign(&mut self, rhs: Int64) {
+        *self = *self % rhs;
+    }
+}
+forward_ref_op_assign!(impl RemAssign, rem_assign for Int64, Int64);
+
+impl Mul<Int64> for Int64 {
+    type Output = Self;
+
+    fn mul(self, rhs: Self) -> Self::Output {
+        Self(self.0.checked_mul(rhs.0).unwrap())
+    }
+}
+forward_ref_binop!(impl Mul, mul for Int64, Int64);
+
+impl MulAssign<Int64> for Int64 {
+    fn mul_assign(&mut self, rhs: Self) {
+        self.0 = self.0.checked_mul(rhs.0).unwrap();
+    }
+}
+forward_ref_op_assign!(impl MulAssign, mul_assign for Int64, Int64);
+
+impl Shr<u32> for Int64 {
+    type Output = Self;
+
+    fn shr(self, rhs: u32) -> Self::Output {
+        self.checked_shr(rhs).unwrap_or_else(|_| {
+            panic!("right shift error: {rhs} is larger or equal than the number of bits in Int64",)
+        })
+    }
+}
+forward_ref_binop!(impl Shr, shr for Int64, u32);
+
+impl Shl<u32> for Int64 {
+    type Output = Self;
+
+    fn shl(self, rhs: u32) -> Self::Output {
+        self.checked_shl(rhs).unwrap_or_else(|_| {
+            panic!("left shift error: {rhs} is larger or equal than the number of bits in Int64",)
+        })
+    }
+}
+forward_ref_binop!(impl Shl, shl for Int64, u32);
+
+impl AddAssign<Int64> for Int64 {
+    fn add_assign(&mut self, rhs: Int64) {
+        self.0 = self.0.checked_add(rhs.0).unwrap();
+    }
+}
+forward_ref_op_assign!(impl AddAssign, add_assign for Int64, Int64);
+
+impl DivAssign<Int64> for Int64 {
+    fn div_assign(&mut self, rhs: Self) {
+        self.0 = self.0.checked_div(rhs.0).unwrap();
+    }
+}
+forward_ref_op_assign!(impl DivAssign, div_assign for Int64, Int64);
+
+impl ShrAssign<u32> for Int64 {
+    fn shr_assign(&mut self, rhs: u32) {
+        *self = Shr::<u32>::shr(*self, rhs);
+    }
+}
+forward_ref_op_assign!(impl ShrAssign, shr_assign for Int64, u32);
+
+impl ShlAssign<u32> for Int64 {
+    fn shl_assign(&mut self, rhs: u32) {
+        *self = Shl::<u32>::shl(*self, rhs);
+    }
+}
+forward_ref_op_assign!(impl ShlAssign, shl_assign for Int64, u32);
+
+impl Serialize for Int64 {
+    /// Serializes as an integer string using base 10
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: ser::Serializer,
+    {
+        serializer.serialize_str(&self.to_string())
+    }
+}
+
+impl<'de> Deserialize<'de> for Int64 {
+    /// Deserialized from an integer string using base 10
+    fn deserialize<D>(deserializer: D) -> Result<Int64, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        deserializer.deserialize_str(Int64Visitor)
+    }
+}
+
+struct Int64Visitor;
+
+impl<'de> de::Visitor<'de> for Int64Visitor {
+    type Value = Int64;
+
+    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+        formatter.write_str("string-encoded integer")
+    }
+
+    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
+    where
+        E: de::Error,
+    {
+        Int64::try_from(v).map_err(|e| E::custom(format!("invalid Int64 '{v}' - {e}")))
+    }
+}
+
+impl<A> std::iter::Sum<A> for Int64
+where
+    Self: Add<A, Output = Self>,
+{
+    fn sum<I: Iterator<Item = A>>(iter: I) -> Self {
+        iter.fold(Self::zero(), Add::add)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::{from_slice, to_vec};
+
+    #[test]
+    fn size_of_works() {
+        assert_eq!(std::mem::size_of::<Int64>(), 8);
+    }
+
+    #[test]
+    fn int64_from_be_bytes_works() {
+        let num = Int64::from_be_bytes([1; 8]);
+        let a: [u8; 8] = num.to_be_bytes();
+        assert_eq!(a, [1; 8]);
+
+        let be_bytes = [0u8, 222u8, 0u8, 0u8, 0u8, 1u8, 2u8, 3u8];
+        let num = Int64::from_be_bytes(be_bytes);
+        let resulting_bytes: [u8; 8] = num.to_be_bytes();
+        assert_eq!(be_bytes, resulting_bytes);
+    }
+
+    #[test]
+    fn int64_new_works() {
+        let num = Int64::new(222);
+        assert_eq!(num.i64(), 222);
+
+        let num = Int64::new(-222);
+        assert_eq!(num.i64(), -222);
+
+        let num = Int64::new(i64::MAX);
+        assert_eq!(num.i64(), i64::MAX);
+
+        let num = Int64::new(i64::MIN);
+        assert_eq!(num.i64(), i64::MIN);
+    }
+
+    #[test]
+    fn int64_zero_works() {
+        let zero = Int64::zero();
+        assert_eq!(zero.to_be_bytes(), [0; 8]);
+    }
+
+    #[test]
+    fn uint64_one_works() {
+        let one = Int64::one();
+        let mut one_be = [0; 8];
+        one_be[7] = 1;
+
+        assert_eq!(one.to_be_bytes(), one_be);
+    }
+
+    #[test]
+    fn int64_endianness() {
+        let be_bytes = [0u8, 0u8, 0u8, 0u8, 0u8, 1u8, 2u8, 3u8];
+        let le_bytes = [3u8, 2u8, 1u8, 0u8, 0u8, 0u8, 0u8, 0u8];
+
+        // These should all be the same.
+        let num1 = Int64::from_be_bytes(be_bytes);
+        let num2 = Int64::from_le_bytes(le_bytes);
+        assert_eq!(num1, Int64::from(65536u32 + 512 + 3));
+        assert_eq!(num1, num2);
+    }
+
+    #[test]
+    fn int64_convert_from() {
+        let a = Int64::from(5i64);
+        assert_eq!(a.0, i64::from(5u32));
+
+        let a = Int64::from(5i64);
+        assert_eq!(a.0, i64::from(5u32));
+
+        let a = Int64::from(5u32);
+        assert_eq!(a.0, i64::from(5u32));
+
+        let a = Int64::from(5u16);
+        assert_eq!(a.0, i64::from(5u32));
+
+        let a = Int64::from(5u8);
+        assert_eq!(a.0, i64::from(5u32));
+
+        let a = Int64::from(-5i64);
+        assert_eq!(a.0, i64::from(-5i32));
+
+        let a = Int64::from(-5i64);
+        assert_eq!(a.0, i64::from(-5i32));
+
+        let a = Int64::from(-5i32);
+        assert_eq!(a.0, i64::from(-5i32));
+
+        let a = Int64::from(-5i16);
+        assert_eq!(a.0, i64::from(-5i32));
+
+        let a = Int64::from(-5i8);
+        assert_eq!(a.0, i64::from(-5i32));
+
+        let result = Int64::try_from("34567");
+        assert_eq!(result.unwrap().0, "34567".parse::<i64>().unwrap());
+
+        let result = Int64::try_from("1.23");
+        assert!(result.is_err());
+    }
+
+    #[test]
+    fn int64_implements_display() {
+        let a = Int64::from(12345u32);
+        assert_eq!(format!("Embedded: {a}"), "Embedded: 12345");
+        assert_eq!(a.to_string(), "12345");
+
+        let a = Int64::from(-12345i32);
+        assert_eq!(format!("Embedded: {a}"), "Embedded: -12345");
+        assert_eq!(a.to_string(), "-12345");
+
+        let a = Int64::zero();
+        assert_eq!(format!("Embedded: {a}"), "Embedded: 0");
+        assert_eq!(a.to_string(), "0");
+    }
+
+    #[test]
+    fn int64_display_padding_works() {
+        let a = Int64::from(123i64);
+        assert_eq!(format!("Embedded: {a:05}"), "Embedded: 00123");
+
+        let a = Int64::from(-123i64);
+        assert_eq!(format!("Embedded: {a:05}"), "Embedded: -0123");
+    }
+
+    #[test]
+    fn int64_to_be_bytes_works() {
+        assert_eq!(Int64::zero().to_be_bytes(), [0; 8]);
+
+        let mut max = [0xff; 8];
+        max[0] = 0x7f;
+        assert_eq!(Int64::MAX.to_be_bytes(), max);
+
+        let mut one = [0; 8];
+        one[7] = 1;
+        assert_eq!(Int64::from(1i64).to_be_bytes(), one);
+        // Python: `[b for b in (8535972485454015680).to_bytes(8, "big")]`
+        assert_eq!(
+            Int64::from(8535972485454015680i64).to_be_bytes(),
+            [118, 117, 221, 191, 255, 254, 172, 192]
+        );
+        assert_eq!(
+            Int64::from_be_bytes([17, 4, 23, 32, 87, 67, 123, 200]).to_be_bytes(),
+            [17, 4, 23, 32, 87, 67, 123, 200]
+        );
+    }
+
+    #[test]
+    fn int64_to_le_bytes_works() {
+        assert_eq!(Int64::zero().to_le_bytes(), [0; 8]);
+
+        let mut max = [0xff; 8];
+        max[7] = 0x7f;
+        assert_eq!(Int64::MAX.to_le_bytes(), max);
+
+        let mut one = [0; 8];
+        one[0] = 1;
+        assert_eq!(Int64::from(1i64).to_le_bytes(), one);
+        // Python: `[b for b in (8535972485454015680).to_bytes(8, "little")]`
+        assert_eq!(
+            Int64::from(8535972485454015680i64).to_le_bytes(),
+            [192, 172, 254, 255, 191, 221, 117, 118]
+        );
+        assert_eq!(
+            Int64::from_be_bytes([17, 4, 23, 32, 87, 67, 123, 200]).to_le_bytes(),
+            [200, 123, 67, 87, 32, 23, 4, 17]
+        );
+    }
+
+    #[test]
+    fn int64_is_zero_works() {
+        assert!(Int64::zero().is_zero());
+        assert!(Int64(i64::from(0u32)).is_zero());
+
+        assert!(!Int64::from(1u32).is_zero());
+        assert!(!Int64::from(123u32).is_zero());
+        assert!(!Int64::from(-123i32).is_zero());
+    }
+
+    #[test]
+    fn int64_wrapping_methods() {
+        // wrapping_add
+        assert_eq!(
+            Int64::from(2u32).wrapping_add(Int64::from(2u32)),
+            Int64::from(4u32)
+        ); // non-wrapping
+        assert_eq!(Int64::MAX.wrapping_add(Int64::from(1u32)), Int64::MIN); // wrapping
+
+        // wrapping_sub
+        assert_eq!(
+            Int64::from(7u32).wrapping_sub(Int64::from(5u32)),
+            Int64::from(2u32)
+        ); // non-wrapping
+        assert_eq!(Int64::MIN.wrapping_sub(Int64::from(1u32)), Int64::MAX); // wrapping
+
+        // wrapping_mul
+        assert_eq!(
+            Int64::from(3u32).wrapping_mul(Int64::from(2u32)),
+            Int64::from(6u32)
+        ); // non-wrapping
+        assert_eq!(
+            Int64::MAX.wrapping_mul(Int64::from(2u32)),
+            Int64::from(-2i32)
+        ); // wrapping
+
+        // wrapping_pow
+        assert_eq!(Int64::from(2u32).wrapping_pow(3), Int64::from(8u32)); // non-wrapping
+        assert_eq!(Int64::MAX.wrapping_pow(2), Int64::from(1u32)); // wrapping
+    }
+
+    #[test]
+    fn int64_json() {
+        let orig = Int64::from(1234567890987654321i64);
+        let serialized = to_vec(&orig).unwrap();
+        assert_eq!(serialized.as_slice(), b"\"1234567890987654321\"");
+        let parsed: Int64 = from_slice(&serialized).unwrap();
+        assert_eq!(parsed, orig);
+    }
+
+    #[test]
+    fn int64_compare() {
+        let a = Int64::from(12345u32);
+        let b = Int64::from(23456u32);
+
+        assert!(a < b);
+        assert!(b > a);
+        assert_eq!(a, Int64::from(12345u32));
+    }
+
+    #[test]
+    #[allow(clippy::op_ref)]
+    fn int64_math() {
+        let a = Int64::from(-12345i32);
+        let b = Int64::from(23456u32);
+
+        // test + with owned and reference right hand side
+        assert_eq!(a + b, Int64::from(11111u32));
+        assert_eq!(a + &b, Int64::from(11111u32));
+
+        // test - with owned and reference right hand side
+        assert_eq!(b - a, Int64::from(35801u32));
+        assert_eq!(b - &a, Int64::from(35801u32));
+
+        // test += with owned and reference right hand side
+        let mut c = Int64::from(300000u32);
+        c += b;
+        assert_eq!(c, Int64::from(323456u32));
+        let mut d = Int64::from(300000u32);
+        d += &b;
+        assert_eq!(d, Int64::from(323456u32));
+
+        // test -= with owned and reference right hand side
+        let mut c = Int64::from(300000u32);
+        c -= b;
+        assert_eq!(c, Int64::from(276544u32));
+        let mut d = Int64::from(300000u32);
+        d -= &b;
+        assert_eq!(d, Int64::from(276544u32));
+
+        // test - with negative result
+        assert_eq!(a - b, Int64::from(-35801i32));
+    }
+
+    #[test]
+    #[should_panic]
+    fn int64_add_overflow_panics() {
+        let _ = Int64::MAX + Int64::from(12u32);
+    }
+
+    #[test]
+    #[allow(clippy::op_ref)]
+    fn int64_sub_works() {
+        assert_eq!(Int64::from(2u32) - Int64::from(1u32), Int64::from(1u32));
+        assert_eq!(Int64::from(2u32) - Int64::from(0u32), Int64::from(2u32));
+        assert_eq!(Int64::from(2u32) - Int64::from(2u32), Int64::from(0u32));
+        assert_eq!(Int64::from(2u32) - Int64::from(3u32), Int64::from(-1i32));
+
+        // works for refs
+        let a = Int64::from(10u32);
+        let b = Int64::from(3u32);
+        let expected = Int64::from(7u32);
+        assert_eq!(a - b, expected);
+        assert_eq!(a - &b, expected);
+        assert_eq!(&a - b, expected);
+        assert_eq!(&a - &b, expected);
+    }
+
+    #[test]
+    #[should_panic]
+    fn int64_sub_overflow_panics() {
+        let _ = Int64::MIN + Int64::one() - Int64::from(2u32);
+    }
+
+    #[test]
+    fn int64_sub_assign_works() {
+        let mut a = Int64::from(14u32);
+        a -= Int64::from(2u32);
+        assert_eq!(a, Int64::from(12u32));
+
+        // works for refs
+        let mut a = Int64::from(10u32);
+        let b = Int64::from(3u32);
+        let expected = Int64::from(7u32);
+        a -= &b;
+        assert_eq!(a, expected);
+    }
+
+    #[test]
+    #[allow(clippy::op_ref)]
+    fn int64_mul_works() {
+        assert_eq!(Int64::from(2u32) * Int64::from(3u32), Int64::from(6u32));
+        assert_eq!(Int64::from(2u32) * Int64::zero(), Int64::zero());
+
+        // works for refs
+        let a = Int64::from(11u32);
+        let b = Int64::from(3u32);
+        let expected = Int64::from(33u32);
+        assert_eq!(a * b, expected);
+        assert_eq!(a * &b, expected);
+        assert_eq!(&a * b, expected);
+        assert_eq!(&a * &b, expected);
+    }
+
+    #[test]
+    fn int64_mul_assign_works() {
+        let mut a = Int64::from(14u32);
+        a *= Int64::from(2u32);
+        assert_eq!(a, Int64::from(28u32));
+
+        // works for refs
+        let mut a = Int64::from(10u32);
+        let b = Int64::from(3u32);
+        a *= &b;
+        assert_eq!(a, Int64::from(30u32));
+    }
+
+    #[test]
+    fn int64_pow_works() {
+        assert_eq!(Int64::from(2u32).pow(2), Int64::from(4u32));
+        assert_eq!(Int64::from(2u32).pow(10), Int64::from(1024u32));
+    }
+
+    #[test]
+    #[should_panic]
+    fn int64_pow_overflow_panics() {
+        _ = Int64::MAX.pow(2u32);
+    }
+
+    #[test]
+    fn int64_shr_works() {
+        let original = Int64::from_be_bytes([0u8, 0u8, 0u8, 0u8, 2u8, 0u8, 4u8, 2u8]);
+
+        let shifted = Int64::from_be_bytes([0u8, 0u8, 0u8, 0u8, 0u8, 128u8, 1u8, 0u8]);
+
+        assert_eq!(original >> 2u32, shifted);
+    }
+
+    #[test]
+    #[should_panic]
+    fn int64_shr_overflow_panics() {
+        let _ = Int64::from(1u32) >> 64u32;
+    }
+
+    #[test]
+    fn sum_works() {
+        let nums = vec![
+            Int64::from(17u32),
+            Int64::from(123u32),
+            Int64::from(540u32),
+            Int64::from(82u32),
+        ];
+        let expected = Int64::from(762u32);
+
+        let sum_as_ref: Int64 = nums.iter().sum();
+        assert_eq!(expected, sum_as_ref);
+
+        let sum_as_owned: Int64 = nums.into_iter().sum();
+        assert_eq!(expected, sum_as_owned);
+    }
+
+    #[test]
+    fn int64_methods() {
+        // checked_*
+        assert!(matches!(
+            Int64::MAX.checked_add(Int64::from(1u32)),
+            Err(OverflowError { .. })
+        ));
+        assert_eq!(
+            Int64::from(1u32).checked_add(Int64::from(1u32)),
+            Ok(Int64::from(2u32)),
+        );
+        assert!(matches!(
+            Int64::MIN.checked_sub(Int64::from(1u32)),
+            Err(OverflowError { .. })
+        ));
+        assert_eq!(
+            Int64::from(2u32).checked_sub(Int64::from(1u32)),
+            Ok(Int64::from(1u32)),
+        );
+        assert!(matches!(
+            Int64::MAX.checked_mul(Int64::from(2u32)),
+            Err(OverflowError { .. })
+        ));
+        assert_eq!(
+            Int64::from(2u32).checked_mul(Int64::from(2u32)),
+            Ok(Int64::from(4u32)),
+        );
+        assert!(matches!(
+            Int64::MAX.checked_pow(2u32),
+            Err(OverflowError { .. })
+        ));
+        assert_eq!(Int64::from(2u32).checked_pow(3u32), Ok(Int64::from(8u32)),);
+        assert_eq!(
+            Int64::MAX.checked_div(Int64::from(0u32)),
+            Err(DivisionError::DivideByZero)
+        );
+        assert_eq!(
+            Int64::from(6u32).checked_div(Int64::from(2u32)),
+            Ok(Int64::from(3u32)),
+        );
+        assert_eq!(
+            Int64::MAX.checked_div_euclid(Int64::from(0u32)),
+            Err(DivisionError::DivideByZero)
+        );
+        assert_eq!(
+            Int64::from(6u32).checked_div_euclid(Int64::from(2u32)),
+            Ok(Int64::from(3u32)),
+        );
+        assert_eq!(
+            Int64::from(7u32).checked_div_euclid(Int64::from(2u32)),
+            Ok(Int64::from(3u32)),
+        );
+        assert!(matches!(
+            Int64::MAX.checked_rem(Int64::from(0u32)),
+            Err(DivideByZeroError { .. })
+        ));
+        // checked_* with negative numbers
+        assert_eq!(
+            Int64::from(-12i32).checked_div(Int64::from(10i32)),
+            Ok(Int64::from(-1i32)),
+        );
+        assert_eq!(Int64::from(-2i32).checked_pow(3u32), Ok(Int64::from(-8i32)),);
+        assert_eq!(
+            Int64::from(-6i32).checked_mul(Int64::from(-7i32)),
+            Ok(Int64::from(42i32)),
+        );
+        assert_eq!(
+            Int64::from(-2i32).checked_add(Int64::from(3i32)),
+            Ok(Int64::from(1i32)),
+        );
+        assert_eq!(
+            Int64::from(-1i32).checked_div_euclid(Int64::from(-2i32)),
+            Ok(Int64::from(1u32)),
+        );
+
+        // saturating_*
+        assert_eq!(Int64::MAX.saturating_add(Int64::from(1u32)), Int64::MAX);
+        assert_eq!(Int64::MIN.saturating_sub(Int64::from(1u32)), Int64::MIN);
+        assert_eq!(Int64::MAX.saturating_mul(Int64::from(2u32)), Int64::MAX);
+        assert_eq!(Int64::from(4u32).saturating_pow(2u32), Int64::from(16u32));
+        assert_eq!(Int64::MAX.saturating_pow(2u32), Int64::MAX);
+    }
+
+    #[test]
+    #[allow(clippy::op_ref)]
+    fn int64_implements_rem() {
+        let a = Int64::from(10u32);
+        assert_eq!(a % Int64::from(10u32), Int64::zero());
+        assert_eq!(a % Int64::from(2u32), Int64::zero());
+        assert_eq!(a % Int64::from(1u32), Int64::zero());
+        assert_eq!(a % Int64::from(3u32), Int64::from(1u32));
+        assert_eq!(a % Int64::from(4u32), Int64::from(2u32));
+
+        assert_eq!(Int64::from(-12i32) % Int64::from(10i32), Int64::from(-2i32));
+        assert_eq!(Int64::from(12i32) % Int64::from(-10i32), Int64::from(2i32));
+        assert_eq!(
+            Int64::from(-12i32) % Int64::from(-10i32),
+            Int64::from(-2i32)
+        );
+
+        // works for refs
+        let a = Int64::from(10u32);
+        let b = Int64::from(3u32);
+        let expected = Int64::from(1u32);
+        assert_eq!(a % b, expected);
+        assert_eq!(a % &b, expected);
+        assert_eq!(&a % b, expected);
+        assert_eq!(&a % &b, expected);
+    }
+
+    #[test]
+    #[should_panic(expected = "divisor of zero")]
+    fn int64_rem_panics_for_zero() {
+        let _ = Int64::from(10u32) % Int64::zero();
+    }
+
+    #[test]
+    fn int64_rem_assign_works() {
+        let mut a = Int64::from(30u32);
+        a %= Int64::from(4u32);
+        assert_eq!(a, Int64::from(2u32));
+
+        // works for refs
+        let mut a = Int64::from(25u32);
+        let b = Int64::from(6u32);
+        a %= &b;
+        assert_eq!(a, Int64::from(1u32));
+    }
+
+    #[test]
+    fn int64_shr() {
+        let x: Int64 = 0x4000_0000_0000_0000i64.into();
+        assert_eq!(x >> 0, x); // right shift by 0 should be no-op
+        assert_eq!(x >> 1, Int64::from(0x2000_0000_0000_0000i64));
+        assert_eq!(x >> 4, Int64::from(0x0400_0000_0000_0000i64));
+        // right shift of MIN value by the maximum shift value should result in -1 (filled with 1s)
+        assert_eq!(
+            Int64::MIN >> (std::mem::size_of::<Int64>() as u32 * 8 - 1),
+            -Int64::one()
+        );
+    }
+
+    #[test]
+    fn int64_shl() {
+        let x: Int64 = 0x0800_0000_0000_0000i64.into();
+        assert_eq!(x << 0, x); // left shift by 0 should be no-op
+        assert_eq!(x << 1, Int64::from(0x1000_0000_0000_0000i64));
+        assert_eq!(x << 4, Int64::from(0x0800_0000_0000_0000i64 << 4));
+        // left shift by by the maximum shift value should result in MIN
+        assert_eq!(
+            Int64::one() << (std::mem::size_of::<Int64>() as u32 * 8 - 1),
+            Int64::MIN
+        );
+    }
+
+    #[test]
+    fn int64_abs_diff_works() {
+        let a = Int64::from(42u32);
+        let b = Int64::from(5u32);
+        let expected = Uint64::from(37u32);
+        assert_eq!(a.abs_diff(b), expected);
+        assert_eq!(b.abs_diff(a), expected);
+
+        let c = Int64::from(-5i32);
+        assert_eq!(b.abs_diff(c), Uint64::from(10u32));
+        assert_eq!(c.abs_diff(b), Uint64::from(10u32));
+    }
+
+    #[test]
+    #[should_panic = "attempt to negate with overflow"]
+    fn int64_neg_min_panics() {
+        _ = -Int64::MIN;
+    }
+
+    #[test]
+    fn int64_partial_eq() {
+        let test_cases = [(1, 1, true), (42, 42, true), (42, 24, false), (0, 0, true)]
+            .into_iter()
+            .map(|(lhs, rhs, expected): (i64, i64, bool)| {
+                (Int64::from(lhs), Int64::from(rhs), expected)
+            });
+
+        #[allow(clippy::op_ref)]
+        for (lhs, rhs, expected) in test_cases {
+            assert_eq!(lhs == rhs, expected);
+            assert_eq!(&lhs == rhs, expected);
+            assert_eq!(lhs == &rhs, expected);
+            assert_eq!(&lhs == &rhs, expected);
+        }
+    }
+}
diff --git a/packages/std/src/math/mod.rs b/packages/std/src/math/mod.rs
index 72e55d9f1..782276af3 100644
--- a/packages/std/src/math/mod.rs
+++ b/packages/std/src/math/mod.rs
@@ -1,6 +1,10 @@
 mod decimal;
 mod decimal256;
 mod fraction;
+mod int128;
+mod int256;
+mod int512;
+mod int64;
 mod isqrt;
 mod uint128;
 mod uint256;
@@ -10,6 +14,10 @@ mod uint64;
 pub use decimal::{Decimal, DecimalRangeExceeded};
 pub use decimal256::{Decimal256, Decimal256RangeExceeded};
 pub use fraction::Fraction;
+pub use int128::Int128;
+pub use int256::Int256;
+pub use int512::Int512;
+pub use int64::Int64;
 pub use isqrt::Isqrt;
 pub use uint128::Uint128;
 pub use uint256::Uint256;
@@ -64,14 +72,31 @@ mod tests {
     {
     }
 
+    trait SignedImpl<'a>: IntImpl<'a> + Neg<Output = Self> {}
+
     impl AllImpl<'_> for Uint64 {}
     impl AllImpl<'_> for Uint128 {}
     impl AllImpl<'_> for Uint256 {}
     impl AllImpl<'_> for Uint512 {}
+    impl AllImpl<'_> for Int64 {}
+    impl AllImpl<'_> for Int128 {}
+    impl AllImpl<'_> for Int256 {}
+    impl AllImpl<'_> for Int512 {}
+
+    impl IntImpl<'_> for Int64 {}
+    impl IntImpl<'_> for Int128 {}
+    impl IntImpl<'_> for Int256 {}
+    impl IntImpl<'_> for Int512 {}
     impl IntImpl<'_> for Uint64 {}
     impl IntImpl<'_> for Uint128 {}
     impl IntImpl<'_> for Uint256 {}
     impl IntImpl<'_> for Uint512 {}
+
     impl AllImpl<'_> for Decimal {}
     impl AllImpl<'_> for Decimal256 {}
+
+    impl SignedImpl<'_> for Int64 {}
+    impl SignedImpl<'_> for Int128 {}
+    impl SignedImpl<'_> for Int256 {}
+    impl SignedImpl<'_> for Int512 {}
 }
diff --git a/packages/std/src/math/uint128.rs b/packages/std/src/math/uint128.rs
index 6733dfc0c..5bcaed78d 100644
--- a/packages/std/src/math/uint128.rs
+++ b/packages/std/src/math/uint128.rs
@@ -37,7 +37,7 @@ use crate::{
 /// assert_eq!(c.u128(), 70);
 /// ```
 #[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, JsonSchema)]
-pub struct Uint128(#[schemars(with = "String")] u128);
+pub struct Uint128(#[schemars(with = "String")] pub(crate) u128);
 
 forward_ref_partial_eq!(Uint128, Uint128);
 
diff --git a/packages/std/src/math/uint256.rs b/packages/std/src/math/uint256.rs
index d0d7fe94c..9f71573c4 100644
--- a/packages/std/src/math/uint256.rs
+++ b/packages/std/src/math/uint256.rs
@@ -14,18 +14,9 @@ use crate::errors::{
 };
 use crate::{forward_ref_partial_eq, impl_mul_fraction, Fraction, Uint128, Uint512, Uint64};
 
-/// This module is purely a workaround that lets us ignore lints for all the code
-/// the `construct_uint!` macro generates.
-#[allow(clippy::all)]
-mod uints {
-    uint::construct_uint! {
-        pub struct U256(4);
-    }
-}
-
 /// Used internally - we don't want to leak this type since we might change
 /// the implementation in the future.
-use uints::U256;
+use bnum::types::U256;
 
 /// An implementation of u256 that is using strings for JSON encoding/decoding,
 /// such that the full u256 range can be used for clients that convert JSON numbers to floats,
@@ -48,13 +39,13 @@ use uints::U256;
 /// assert_eq!(a, b);
 /// ```
 #[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, JsonSchema)]
-pub struct Uint256(#[schemars(with = "String")] U256);
+pub struct Uint256(#[schemars(with = "String")] pub(crate) U256);
 
 forward_ref_partial_eq!(Uint256, Uint256);
 
 impl Uint256 {
     pub const MAX: Uint256 = Uint256(U256::MAX);
-    pub const MIN: Uint256 = Uint256(U256::zero());
+    pub const MIN: Uint256 = Uint256(U256::ZERO);
 
     /// Creates a Uint256(value) from a big endian representation. It's just an alias for
     /// [`Uint256::from_be_bytes`].
@@ -67,16 +58,13 @@ impl Uint256 {
     /// Creates a Uint256(0)
     #[inline]
     pub const fn zero() -> Self {
-        Uint256(U256::zero())
+        Self(U256::ZERO)
     }
 
     /// Creates a Uint256(1)
     #[inline]
     pub const fn one() -> Self {
-        Self::from_be_bytes([
-            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-            0, 0, 1,
-        ])
+        Self(U256::ONE)
     }
 
     #[must_use]
@@ -95,7 +83,7 @@ impl Uint256 {
                 data[7], data[6], data[5], data[4], data[3], data[2], data[1], data[0],
             ]),
         ];
-        Self(U256(words))
+        Self(U256::from_digits(words))
     }
 
     #[must_use]
@@ -114,7 +102,7 @@ impl Uint256 {
                 data[24], data[25], data[26], data[27], data[28], data[29], data[30], data[31],
             ]),
         ];
-        Uint256(U256(words))
+        Self(U256::from_digits(words))
     }
 
     /// A conversion from `u128` that, unlike the one provided by the `From` trait,
@@ -140,11 +128,12 @@ impl Uint256 {
     /// Returns a copy of the number as big endian bytes.
     #[must_use = "this returns the result of the operation, without modifying the original"]
     pub const fn to_be_bytes(self) -> [u8; 32] {
+        let words = self.0.digits();
         let words = [
-            (self.0).0[3].to_be_bytes(),
-            (self.0).0[2].to_be_bytes(),
-            (self.0).0[1].to_be_bytes(),
-            (self.0).0[0].to_be_bytes(),
+            words[3].to_be_bytes(),
+            words[2].to_be_bytes(),
+            words[1].to_be_bytes(),
+            words[0].to_be_bytes(),
         ];
         unsafe { std::mem::transmute::<[[u8; 8]; 4], [u8; 32]>(words) }
     }
@@ -152,25 +141,24 @@ impl Uint256 {
     /// Returns a copy of the number as little endian bytes.
     #[must_use = "this returns the result of the operation, without modifying the original"]
     pub const fn to_le_bytes(self) -> [u8; 32] {
+        let words = self.0.digits();
         let words = [
-            (self.0).0[0].to_le_bytes(),
-            (self.0).0[1].to_le_bytes(),
-            (self.0).0[2].to_le_bytes(),
-            (self.0).0[3].to_le_bytes(),
+            words[0].to_le_bytes(),
+            words[1].to_le_bytes(),
+            words[2].to_le_bytes(),
+            words[3].to_le_bytes(),
         ];
         unsafe { std::mem::transmute::<[[u8; 8]; 4], [u8; 32]>(words) }
     }
 
     #[must_use]
     pub const fn is_zero(&self) -> bool {
-        let words = (self.0).0;
-        words[0] == 0 && words[1] == 0 && words[2] == 0 && words[3] == 0
+        self.0.is_zero()
     }
 
     #[must_use = "this returns the result of the operation, without modifying the original"]
     pub fn pow(self, exp: u32) -> Self {
-        let res = self.0.pow(exp.into());
-        Self(res)
+        Self(self.0.pow(exp))
     }
 
     /// Returns `self * numerator / denominator`.
@@ -257,7 +245,7 @@ impl Uint256 {
 
     pub fn checked_pow(self, exp: u32) -> Result<Self, OverflowError> {
         self.0
-            .checked_pow(exp.into())
+            .checked_pow(exp)
             .map(Self)
             .ok_or_else(|| OverflowError::new(OverflowOperation::Pow, self, exp))
     }
@@ -299,29 +287,25 @@ impl Uint256 {
     #[must_use = "this returns the result of the operation, without modifying the original"]
     #[inline]
     pub fn wrapping_add(self, other: Self) -> Self {
-        let (value, _did_overflow) = self.0.overflowing_add(other.0);
-        Self(value)
+        Self(self.0.wrapping_add(other.0))
     }
 
     #[must_use = "this returns the result of the operation, without modifying the original"]
     #[inline]
     pub fn wrapping_sub(self, other: Self) -> Self {
-        let (value, _did_overflow) = self.0.overflowing_sub(other.0);
-        Self(value)
+        Self(self.0.wrapping_sub(other.0))
     }
 
     #[must_use = "this returns the result of the operation, without modifying the original"]
     #[inline]
     pub fn wrapping_mul(self, other: Self) -> Self {
-        let (value, _did_overflow) = self.0.overflowing_mul(other.0);
-        Self(value)
+        Self(self.0.wrapping_mul(other.0))
     }
 
     #[must_use = "this returns the result of the operation, without modifying the original"]
     #[inline]
     pub fn wrapping_pow(self, other: u32) -> Self {
-        let (value, _did_overflow) = self.0.overflowing_pow(other.into());
-        Self(value)
+        Self(self.0.wrapping_pow(other))
     }
 
     #[must_use = "this returns the result of the operation, without modifying the original"]
@@ -341,19 +325,12 @@ impl Uint256 {
 
     #[must_use = "this returns the result of the operation, without modifying the original"]
     pub fn saturating_pow(self, exp: u32) -> Self {
-        match self.checked_pow(exp) {
-            Ok(value) => value,
-            Err(_) => Self::MAX,
-        }
+        Self(self.0.saturating_pow(exp))
     }
 
     #[must_use = "this returns the result of the operation, without modifying the original"]
     pub fn abs_diff(self, other: Self) -> Self {
-        if self < other {
-            other - self
-        } else {
-            self - other
-        }
+        Self(self.0.abs_diff(other.0))
     }
 }
 
@@ -427,7 +404,7 @@ impl FromStr for Uint256 {
             return Err(StdError::generic_err("Parsing u256: received empty string"));
         }
 
-        match U256::from_dec_str(s) {
+        match U256::from_str_radix(s, 10) {
             Ok(u) => Ok(Uint256(u)),
             Err(e) => Err(StdError::generic_err(format!("Parsing u256: {e}"))),
         }
@@ -1046,22 +1023,25 @@ mod tests {
     #[test]
     fn uint256_convert_from() {
         let a = Uint256::from(5u128);
-        assert_eq!(a.0, U256::from(5));
+        assert_eq!(a.0, U256::from(5u32));
 
         let a = Uint256::from(5u64);
-        assert_eq!(a.0, U256::from(5));
+        assert_eq!(a.0, U256::from(5u32));
 
         let a = Uint256::from(5u32);
-        assert_eq!(a.0, U256::from(5));
+        assert_eq!(a.0, U256::from(5u32));
 
         let a = Uint256::from(5u16);
-        assert_eq!(a.0, U256::from(5));
+        assert_eq!(a.0, U256::from(5u32));
 
         let a = Uint256::from(5u8);
-        assert_eq!(a.0, U256::from(5));
+        assert_eq!(a.0, U256::from(5u32));
 
         let result = Uint256::try_from("34567");
-        assert_eq!(result.unwrap().0, U256::from_dec_str("34567").unwrap());
+        assert_eq!(
+            result.unwrap().0,
+            U256::from_str_radix("34567", 10).unwrap()
+        );
 
         let result = Uint256::try_from("1.23");
         assert!(result.is_err());
@@ -1221,7 +1201,7 @@ mod tests {
     #[test]
     fn uint256_is_zero_works() {
         assert!(Uint256::zero().is_zero());
-        assert!(Uint256(U256::from(0)).is_zero());
+        assert!(Uint256(U256::from(0u32)).is_zero());
 
         assert!(!Uint256::from(1u32).is_zero());
         assert!(!Uint256::from(123u32).is_zero());
@@ -1642,7 +1622,7 @@ mod tests {
     }
 
     #[test]
-    #[should_panic(expected = "division by zero")]
+    #[should_panic(expected = "divisor of zero")]
     fn uint256_rem_panics_for_zero() {
         let _ = Uint256::from(10u32) % Uint256::zero();
     }
diff --git a/packages/std/src/math/uint512.rs b/packages/std/src/math/uint512.rs
index a9eb33e08..5d581e05c 100644
--- a/packages/std/src/math/uint512.rs
+++ b/packages/std/src/math/uint512.rs
@@ -3,8 +3,8 @@ use schemars::JsonSchema;
 use serde::{de, ser, Deserialize, Deserializer, Serialize};
 use std::fmt;
 use std::ops::{
-    Add, AddAssign, Div, DivAssign, Mul, MulAssign, Rem, RemAssign, Shl, ShlAssign, Shr, ShrAssign,
-    Sub, SubAssign,
+    Add, AddAssign, Div, DivAssign, Mul, MulAssign, Not, Rem, RemAssign, Shl, ShlAssign, Shr,
+    ShrAssign, Sub, SubAssign,
 };
 use std::str::FromStr;
 
@@ -13,18 +13,9 @@ use crate::errors::{
 };
 use crate::{forward_ref_partial_eq, Uint128, Uint256, Uint64};
 
-/// This module is purely a workaround that lets us ignore lints for all the code
-/// the `construct_uint!` macro generates.
-#[allow(clippy::all)]
-mod uints {
-    uint::construct_uint! {
-        pub struct U512(8);
-    }
-}
-
 /// Used internally - we don't want to leak this type since we might change
 /// the implementation in the future.
-use uints::U512;
+use bnum::types::U512;
 
 /// An implementation of u512 that is using strings for JSON encoding/decoding,
 /// such that the full u512 range can be used for clients that convert JSON numbers to floats,
@@ -51,16 +42,16 @@ use uints::U512;
 /// assert_eq!(a, b);
 /// ```
 #[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, JsonSchema)]
-pub struct Uint512(#[schemars(with = "String")] U512);
+pub struct Uint512(#[schemars(with = "String")] pub(crate) U512);
 
 forward_ref_partial_eq!(Uint512, Uint512);
 
 impl Uint512 {
     pub const MAX: Uint512 = Uint512(U512::MAX);
-    pub const MIN: Uint512 = Uint512(U512::zero());
+    pub const MIN: Uint512 = Uint512(U512::ZERO);
 
     /// Creates a Uint512(value) from a big endian representation. It's just an alias for
-    /// `from_big_endian`.
+    /// `from_be_bytes`.
     pub const fn new(value: [u8; 64]) -> Self {
         Self::from_be_bytes(value)
     }
@@ -68,17 +59,13 @@ impl Uint512 {
     /// Creates a Uint512(0)
     #[inline]
     pub const fn zero() -> Self {
-        Uint512(U512::zero())
+        Uint512(U512::ZERO)
     }
 
     /// Creates a Uint512(1)
     #[inline]
     pub const fn one() -> Self {
-        Self::from_be_bytes([
-            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-            0, 0, 0, 0, 0, 1,
-        ])
+        Self(U512::ONE)
     }
 
     #[must_use]
@@ -109,7 +96,7 @@ impl Uint512 {
                 data[7], data[6], data[5], data[4], data[3], data[2], data[1], data[0],
             ]),
         ];
-        Self(U512(words))
+        Self(U512::from_digits(words))
     }
 
     #[must_use]
@@ -140,7 +127,7 @@ impl Uint512 {
                 data[56], data[57], data[58], data[59], data[60], data[61], data[62], data[63],
             ]),
         ];
-        Self(U512(words))
+        Self(U512::from_digits(words))
     }
 
     /// A conversion from `Uint256` that, unlike the one provided by the `From` trait,
@@ -161,15 +148,16 @@ impl Uint512 {
     /// Returns a copy of the number as big endian bytes.
     #[must_use = "this returns the result of the operation, without modifying the original"]
     pub const fn to_be_bytes(self) -> [u8; 64] {
+        let words = self.0.digits();
         let words = [
-            (self.0).0[7].to_be_bytes(),
-            (self.0).0[6].to_be_bytes(),
-            (self.0).0[5].to_be_bytes(),
-            (self.0).0[4].to_be_bytes(),
-            (self.0).0[3].to_be_bytes(),
-            (self.0).0[2].to_be_bytes(),
-            (self.0).0[1].to_be_bytes(),
-            (self.0).0[0].to_be_bytes(),
+            words[7].to_be_bytes(),
+            words[6].to_be_bytes(),
+            words[5].to_be_bytes(),
+            words[4].to_be_bytes(),
+            words[3].to_be_bytes(),
+            words[2].to_be_bytes(),
+            words[1].to_be_bytes(),
+            words[0].to_be_bytes(),
         ];
         unsafe { std::mem::transmute::<[[u8; 8]; 8], [u8; 64]>(words) }
     }
@@ -177,36 +165,28 @@ impl Uint512 {
     /// Returns a copy of the number as little endian bytes.
     #[must_use = "this returns the result of the operation, without modifying the original"]
     pub const fn to_le_bytes(self) -> [u8; 64] {
+        let words = self.0.digits();
         let words = [
-            (self.0).0[0].to_le_bytes(),
-            (self.0).0[1].to_le_bytes(),
-            (self.0).0[2].to_le_bytes(),
-            (self.0).0[3].to_le_bytes(),
-            (self.0).0[4].to_le_bytes(),
-            (self.0).0[5].to_le_bytes(),
-            (self.0).0[6].to_le_bytes(),
-            (self.0).0[7].to_le_bytes(),
+            words[0].to_le_bytes(),
+            words[1].to_le_bytes(),
+            words[2].to_le_bytes(),
+            words[3].to_le_bytes(),
+            words[4].to_le_bytes(),
+            words[5].to_le_bytes(),
+            words[6].to_le_bytes(),
+            words[7].to_le_bytes(),
         ];
         unsafe { std::mem::transmute::<[[u8; 8]; 8], [u8; 64]>(words) }
     }
 
     #[must_use]
     pub const fn is_zero(&self) -> bool {
-        let words = (self.0).0;
-        words[0] == 0
-            && words[1] == 0
-            && words[2] == 0
-            && words[3] == 0
-            && words[4] == 0
-            && words[5] == 0
-            && words[6] == 0
-            && words[7] == 0
+        self.0.is_zero()
     }
 
     #[must_use = "this returns the result of the operation, without modifying the original"]
     pub fn pow(self, exp: u32) -> Self {
-        let res = self.0.pow(exp.into());
-        Self(res)
+        Self(self.0.pow(exp))
     }
 
     pub fn checked_add(self, other: Self) -> Result<Self, OverflowError> {
@@ -232,7 +212,7 @@ impl Uint512 {
 
     pub fn checked_pow(self, exp: u32) -> Result<Self, OverflowError> {
         self.0
-            .checked_pow(exp.into())
+            .checked_pow(exp)
             .map(Self)
             .ok_or_else(|| OverflowError::new(OverflowOperation::Pow, self, exp))
     }
@@ -256,11 +236,10 @@ impl Uint512 {
     }
 
     pub fn checked_shr(self, other: u32) -> Result<Self, OverflowError> {
-        if other >= 512 {
-            return Err(OverflowError::new(OverflowOperation::Shr, self, other));
-        }
-
-        Ok(Self(self.0.shr(other)))
+        self.0
+            .checked_shr(other)
+            .map(Self)
+            .ok_or_else(|| OverflowError::new(OverflowOperation::Shr, self, other))
     }
 
     pub fn checked_shl(self, other: u32) -> Result<Self, OverflowError> {
@@ -274,29 +253,25 @@ impl Uint512 {
     #[must_use = "this returns the result of the operation, without modifying the original"]
     #[inline]
     pub fn wrapping_add(self, other: Self) -> Self {
-        let (value, _did_overflow) = self.0.overflowing_add(other.0);
-        Self(value)
+        Self(self.0.wrapping_add(other.0))
     }
 
     #[must_use = "this returns the result of the operation, without modifying the original"]
     #[inline]
     pub fn wrapping_sub(self, other: Self) -> Self {
-        let (value, _did_overflow) = self.0.overflowing_sub(other.0);
-        Self(value)
+        Self(self.0.wrapping_sub(other.0))
     }
 
     #[must_use = "this returns the result of the operation, without modifying the original"]
     #[inline]
     pub fn wrapping_mul(self, other: Self) -> Self {
-        let (value, _did_overflow) = self.0.overflowing_mul(other.0);
-        Self(value)
+        Self(self.0.wrapping_mul(other.0))
     }
 
     #[must_use = "this returns the result of the operation, without modifying the original"]
     #[inline]
     pub fn wrapping_pow(self, other: u32) -> Self {
-        let (value, _did_overflow) = self.0.overflowing_pow(other.into());
-        Self(value)
+        Self(self.0.wrapping_pow(other))
     }
 
     #[must_use = "this returns the result of the operation, without modifying the original"]
@@ -316,19 +291,12 @@ impl Uint512 {
 
     #[must_use = "this returns the result of the operation, without modifying the original"]
     pub fn saturating_pow(self, exp: u32) -> Self {
-        match self.checked_pow(exp) {
-            Ok(value) => value,
-            Err(_) => Self::MAX,
-        }
+        Self(self.0.saturating_pow(exp))
     }
 
     #[must_use = "this returns the result of the operation, without modifying the original"]
     pub fn abs_diff(self, other: Self) -> Self {
-        if self < other {
-            other - self
-        } else {
-            self - other
-        }
+        Self(self.0.abs_diff(other.0))
     }
 }
 
@@ -424,7 +392,7 @@ impl FromStr for Uint512 {
     type Err = StdError;
 
     fn from_str(s: &str) -> Result<Self, Self::Err> {
-        match U512::from_dec_str(s) {
+        match U512::from_str_radix(s, 10) {
             Ok(u) => Ok(Self(u)),
             Err(e) => Err(StdError::generic_err(format!("Parsing u512: {e}"))),
         }
@@ -508,6 +476,14 @@ impl Rem for Uint512 {
 }
 forward_ref_binop!(impl Rem, rem for Uint512, Uint512);
 
+impl Not for Uint512 {
+    type Output = Self;
+
+    fn not(self) -> Self::Output {
+        Self(!self.0)
+    }
+}
+
 impl RemAssign<Uint512> for Uint512 {
     fn rem_assign(&mut self, rhs: Uint512) {
         *self = *self % rhs;
@@ -742,22 +718,25 @@ mod tests {
     #[test]
     fn uint512_convert_from() {
         let a = Uint512::from(5u128);
-        assert_eq!(a.0, U512::from(5));
+        assert_eq!(a.0, U512::from(5u32));
 
         let a = Uint512::from(5u64);
-        assert_eq!(a.0, U512::from(5));
+        assert_eq!(a.0, U512::from(5u32));
 
         let a = Uint512::from(5u32);
-        assert_eq!(a.0, U512::from(5));
+        assert_eq!(a.0, U512::from(5u32));
 
         let a = Uint512::from(5u16);
-        assert_eq!(a.0, U512::from(5));
+        assert_eq!(a.0, U512::from(5u32));
 
         let a = Uint512::from(5u8);
-        assert_eq!(a.0, U512::from(5));
+        assert_eq!(a.0, U512::from(5u32));
 
         let result = Uint512::try_from("34567");
-        assert_eq!(result.unwrap().0, U512::from_dec_str("34567").unwrap());
+        assert_eq!(
+            result.unwrap().0,
+            U512::from_str_radix("34567", 10).unwrap()
+        );
 
         let result = Uint512::try_from("1.23");
         assert!(result.is_err());
@@ -935,7 +914,7 @@ mod tests {
     #[test]
     fn uint512_is_zero_works() {
         assert!(Uint512::zero().is_zero());
-        assert!(Uint512(U512::from(0)).is_zero());
+        assert!(Uint512(U512::from(0u32)).is_zero());
 
         assert!(!Uint512::from(1u32).is_zero());
         assert!(!Uint512::from(123u32).is_zero());
@@ -1300,7 +1279,7 @@ mod tests {
     }
 
     #[test]
-    #[should_panic(expected = "division by zero")]
+    #[should_panic(expected = "divisor of zero")]
     fn uint512_rem_panics_for_zero() {
         let _ = Uint512::from(10u32) % Uint512::zero();
     }
diff --git a/packages/std/src/math/uint64.rs b/packages/std/src/math/uint64.rs
index cea6cd37f..734b7a248 100644
--- a/packages/std/src/math/uint64.rs
+++ b/packages/std/src/math/uint64.rs
@@ -30,7 +30,7 @@ use crate::{forward_ref_partial_eq, impl_mul_fraction, Fraction, Uint128};
 /// assert_eq!(b.u64(), 70);
 /// ```
 #[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, JsonSchema)]
-pub struct Uint64(#[schemars(with = "String")] u64);
+pub struct Uint64(#[schemars(with = "String")] pub(crate) u64);
 
 forward_ref_partial_eq!(Uint64, Uint64);
 
diff --git a/packages/std/src/query/distribution.rs b/packages/std/src/query/distribution.rs
new file mode 100644
index 000000000..ffe577232
--- /dev/null
+++ b/packages/std/src/query/distribution.rs
@@ -0,0 +1,22 @@
+use schemars::JsonSchema;
+use serde::{Deserialize, Serialize};
+
+use super::query_response::QueryResponseType;
+
+#[non_exhaustive]
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum DistributionQuery {
+    // https://github.com/cosmos/cosmos-sdk/blob/4f6f6c00021f4b5ee486bbb71ae2071a8ceb47c9/x/distribution/types/query.pb.go#L792-L795
+    DelegatorWithdrawAddress { delegator_address: String },
+}
+
+// https://github.com/cosmos/cosmos-sdk/blob/4f6f6c00021f4b5ee486bbb71ae2071a8ceb47c9/x/distribution/types/query.pb.go#L832-L835
+#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+#[non_exhaustive]
+pub struct DelegatorWithdrawAddressResponse {
+    pub withdraw_address: String,
+}
+
+impl QueryResponseType for DelegatorWithdrawAddressResponse {}
diff --git a/packages/std/src/query/mod.rs b/packages/std/src/query/mod.rs
index c88e209b8..5401f9145 100644
--- a/packages/std/src/query/mod.rs
+++ b/packages/std/src/query/mod.rs
@@ -6,6 +6,7 @@ use crate::Binary;
 use crate::Empty;
 
 mod bank;
+mod distribution;
 mod ibc;
 mod query_response;
 mod staking;
@@ -16,6 +17,8 @@ pub use bank::SupplyResponse;
 pub use bank::{AllBalanceResponse, BalanceResponse, BankQuery};
 #[cfg(feature = "cosmwasm_1_3")]
 pub use bank::{AllDenomMetadataResponse, DenomMetadataResponse};
+#[cfg(feature = "cosmwasm_1_3")]
+pub use distribution::{DelegatorWithdrawAddressResponse, DistributionQuery};
 #[cfg(feature = "stargate")]
 pub use ibc::{ChannelResponse, IbcQuery, ListChannelsResponse, PortIdResponse};
 #[cfg(feature = "staking")]
@@ -35,6 +38,8 @@ pub enum QueryRequest<C> {
     Custom(C),
     #[cfg(feature = "staking")]
     Staking(StakingQuery),
+    #[cfg(feature = "cosmwasm_1_3")]
+    Distribution(DistributionQuery),
     /// A Stargate query is encoded the same way as abci_query, with path and protobuf encoded request data.
     /// The format is defined in [ADR-21](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-021-protobuf-query-encoding.md).
     /// The response is protobuf encoded data directly without a JSON response wrapper.
@@ -108,3 +113,10 @@ impl<C: CustomQuery> From<IbcQuery> for QueryRequest<C> {
         QueryRequest::Ibc(msg)
     }
 }
+
+#[cfg(feature = "cosmwasm_1_3")]
+impl<C: CustomQuery> From<DistributionQuery> for QueryRequest<C> {
+    fn from(msg: DistributionQuery) -> Self {
+        QueryRequest::Distribution(msg)
+    }
+}
diff --git a/packages/std/src/results/cosmos_msg.rs b/packages/std/src/results/cosmos_msg.rs
index 2b483a59c..68dbd4f73 100644
--- a/packages/std/src/results/cosmos_msg.rs
+++ b/packages/std/src/results/cosmos_msg.rs
@@ -111,6 +111,13 @@ pub enum DistributionMsg {
         /// The `validator_address`
         validator: String,
     },
+    /// This is translated to a [[MsgFundCommunityPool](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#LL69C1-L76C2).
+    /// `depositor` is automatically filled with the current contract's address.
+    #[cfg(feature = "cosmwasm_1_3")]
+    FundCommunityPool {
+        /// The amount to spend
+        amount: Vec<Coin>,
+    },
 }
 
 fn binary_to_string(data: &Binary, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
@@ -463,6 +470,39 @@ mod tests {
         }
     }
 
+    #[test]
+    #[cfg(feature = "cosmwasm_1_3")]
+    fn msg_distribution_serializes_to_correct_json() {
+        // FundCommunityPool
+        let fund_coins = vec![coin(200, "feathers"), coin(200, "stones")];
+        let fund_msg = DistributionMsg::FundCommunityPool { amount: fund_coins };
+        let fund_json = to_binary(&fund_msg).unwrap();
+        assert_eq!(
+            String::from_utf8_lossy(&fund_json),
+            r#"{"fund_community_pool":{"amount":[{"denom":"feathers","amount":"200"},{"denom":"stones","amount":"200"}]}}"#,
+        );
+
+        // SetWithdrawAddress
+        let set_msg = DistributionMsg::SetWithdrawAddress {
+            address: String::from("withdrawer"),
+        };
+        let set_json = to_binary(&set_msg).unwrap();
+        assert_eq!(
+            String::from_utf8_lossy(&set_json),
+            r#"{"set_withdraw_address":{"address":"withdrawer"}}"#,
+        );
+
+        // WithdrawDelegatorRewards
+        let withdraw_msg = DistributionMsg::WithdrawDelegatorReward {
+            validator: String::from("fancyoperator"),
+        };
+        let withdraw_json = to_binary(&withdraw_msg).unwrap();
+        assert_eq!(
+            String::from_utf8_lossy(&withdraw_json),
+            r#"{"withdraw_delegator_reward":{"validator":"fancyoperator"}}"#
+        );
+    }
+
     #[test]
     fn wasm_msg_debug_decodes_binary_string_when_possible() {
         #[cosmwasm_schema::cw_serde]
diff --git a/packages/std/src/traits.rs b/packages/std/src/traits.rs
index c0442d0ed..3b3812307 100644
--- a/packages/std/src/traits.rs
+++ b/packages/std/src/traits.rs
@@ -21,7 +21,10 @@ use crate::query::{
     DelegationResponse, FullDelegation, StakingQuery, Validator, ValidatorResponse,
 };
 #[cfg(feature = "cosmwasm_1_3")]
-use crate::query::{AllDenomMetadataResponse, DenomMetadataResponse};
+use crate::query::{
+    AllDenomMetadataResponse, DelegatorWithdrawAddressResponse, DenomMetadataResponse,
+    DistributionQuery,
+};
 use crate::results::{ContractResult, Empty, SystemResult};
 use crate::serde::{from_binary, to_binary, to_vec};
 use crate::ContractInfoResponse;
@@ -38,12 +41,11 @@ pub trait Storage {
     /// is not great yet and might not be possible in all backends. But we're trying to get there.
     fn get(&self, key: &[u8]) -> Option<Vec<u8>>;
 
-    #[cfg(feature = "iterator")]
     /// Allows iteration over a set of key/value pairs, either forwards or backwards.
     ///
     /// The bound `start` is inclusive and `end` is exclusive.
-    ///
     /// If `start` is lexicographically greater than or equal to `end`, an empty range is described, mo matter of the order.
+    #[cfg(feature = "iterator")]
     fn range<'a>(
         &'a self,
         start: Option<&[u8]>,
@@ -51,6 +53,40 @@ pub trait Storage {
         order: Order,
     ) -> Box<dyn Iterator<Item = Record> + 'a>;
 
+    /// Allows iteration over a set of keys, either forwards or backwards.
+    ///
+    /// The bound `start` is inclusive and `end` is exclusive.
+    /// If `start` is lexicographically greater than or equal to `end`, an empty range is described, mo matter of the order.
+    ///
+    /// The default implementation uses [`Storage::range`] and discards the values. More efficient
+    /// implementations might be possible depending on the storage.
+    #[cfg(feature = "iterator")]
+    fn range_keys<'a>(
+        &'a self,
+        start: Option<&[u8]>,
+        end: Option<&[u8]>,
+        order: Order,
+    ) -> Box<dyn Iterator<Item = Vec<u8>> + 'a> {
+        Box::new(self.range(start, end, order).map(|(k, _v)| k))
+    }
+
+    /// Allows iteration over a set of values, either forwards or backwards.
+    ///
+    /// The bound `start` is inclusive and `end` is exclusive.
+    /// If `start` is lexicographically greater than or equal to `end`, an empty range is described, mo matter of the order.
+    ///
+    /// The default implementation uses [`Storage::range`] and discards the keys. More efficient implementations
+    /// might be possible depending on the storage.
+    #[cfg(feature = "iterator")]
+    fn range_values<'a>(
+        &'a self,
+        start: Option<&[u8]>,
+        end: Option<&[u8]>,
+        order: Order,
+    ) -> Box<dyn Iterator<Item = Vec<u8>> + 'a> {
+        Box::new(self.range(start, end, order).map(|(_k, v)| v))
+    }
+
     fn set(&mut self, key: &[u8], value: &[u8]);
 
     /// Removes a database entry at `key`.
@@ -242,6 +278,19 @@ impl<'a, C: CustomQuery> QuerierWrapper<'a, C> {
         Ok(res.amount)
     }
 
+    #[cfg(feature = "cosmwasm_1_3")]
+    pub fn query_delegator_withdraw_address(
+        &self,
+        delegator: impl Into<String>,
+    ) -> StdResult<String> {
+        let request = DistributionQuery::DelegatorWithdrawAddress {
+            delegator_address: delegator.into(),
+        }
+        .into();
+        let res: DelegatorWithdrawAddressResponse = self.query(&request)?;
+        Ok(res.withdraw_address)
+    }
+
     #[cfg(feature = "cosmwasm_1_3")]
     pub fn query_denom_metadata(&self, denom: impl Into<String>) -> StdResult<DenomMetadata> {
         let request = BankQuery::DenomMetadata {
diff --git a/packages/storage/Cargo.toml b/packages/storage/Cargo.toml
index b5dbbab45..270906d7e 100644
--- a/packages/storage/Cargo.toml
+++ b/packages/storage/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "cosmwasm-storage"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 authors = ["Ethan Frey <ethanfrey@users.noreply.github.com>"]
 edition = "2021"
 description = "CosmWasm library with useful helpers for Storage patterns"
@@ -16,5 +16,5 @@ iterator = ["cosmwasm-std/iterator"]
 
 [dependencies]
 # Uses the path when built locally; uses the given version from crates.io when published
-cosmwasm-std = { path = "../std", version = "1.2.7", default-features = false }
+cosmwasm-std = { path = "../std", version = "1.3.0-rc.0", default-features = false }
 serde = { version = "1.0.103", default-features = false, features = ["derive", "alloc"] }
diff --git a/packages/vm/Cargo.toml b/packages/vm/Cargo.toml
index 3c36b5b33..a27b8b27a 100644
--- a/packages/vm/Cargo.toml
+++ b/packages/vm/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "cosmwasm-vm"
-version = "1.2.7"
+version = "1.3.0-rc.0"
 authors = ["Ethan Frey <ethanfrey@users.noreply.github.com>"]
 edition = "2021"
 description = "VM bindings to run cosmwams contracts"
@@ -39,11 +39,11 @@ bytes = "1.4.0" # need a higher version than the one required by Wasmer for the
 clru = "0.4.0"
 crc32fast = "1.3.2"
 # Uses the path when built locally; uses the given version from crates.io when published
-cosmwasm-std = { path = "../std", version = "1.2.7", default-features = false }
-cosmwasm-crypto = { path = "../crypto", version = "1.2.7" }
+cosmwasm-std = { path = "../std", version = "1.3.0-rc.0", default-features = false }
+cosmwasm-crypto = { path = "../crypto", version = "1.3.0-rc.0" }
 derivative = "2"
 hex = "0.4"
-parity-wasm = "0.45"
+parity-wasm = { version = "0.45", features = ["sign_ext"] }
 schemars = "0.8.3"
 serde = { version = "1.0.103", default-features = false, features = ["derive", "alloc"] }
 serde_json = "1.0.40"
diff --git a/packages/vm/README.md b/packages/vm/README.md
index 7fc48e561..a50ae5213 100644
--- a/packages/vm/README.md
+++ b/packages/vm/README.md
@@ -75,6 +75,17 @@ docker run --rm -v "$(pwd)":/code \
   && cp artifacts/floaty.wasm packages/vm/testdata/floaty_1.2.wasm
 ```
 
+The `cyberpunk_rust170.wasm` for
+https://github.com/CosmWasm/cosmwasm/issues/1727 is built as follows
+(non-reproducible):
+
+```sh
+cd contracts/cyberpunk
+rm -r target
+RUSTFLAGS='-C link-arg=-s' cargo build --release --lib --target wasm32-unknown-unknown --locked
+cp target/wasm32-unknown-unknown/release/cyberpunk.wasm ../../packages/vm/testdata/cyberpunk_rust170.wasm
+```
+
 ## Testing
 
 By default, this repository is built and tested with the singlepass backend. You
diff --git a/packages/vm/src/cache.rs b/packages/vm/src/cache.rs
index 38772a272..872ec5f19 100644
--- a/packages/vm/src/cache.rs
+++ b/packages/vm/src/cache.rs
@@ -280,6 +280,7 @@ where
 
         // Re-compile from original Wasm bytecode
         let code = self.load_wasm_with_path(&cache.wasm_path, checksum)?;
+        cache.stats.misses = cache.stats.misses.saturating_add(1);
         let (engine, module) = compile(&code, &[])?;
         // Store into the fs cache too
         let module_size = cache.fs_cache.store(checksum, &module)?;
@@ -470,7 +471,7 @@ mod tests {
     use crate::errors::VmError;
     use crate::testing::{mock_backend, mock_env, mock_info, MockApi, MockQuerier, MockStorage};
     use cosmwasm_std::{coins, Empty};
-    use std::fs::{create_dir_all, OpenOptions};
+    use std::fs::{create_dir_all, remove_dir_all, OpenOptions};
     use std::io::Write;
     use tempfile::TempDir;
 
@@ -794,6 +795,36 @@ mod tests {
         assert_eq!(cache.stats().misses, 0);
     }
 
+    #[test]
+    fn get_instance_recompiles_module() {
+        let options = make_testing_options();
+        let cache = unsafe { Cache::new(options.clone()).unwrap() };
+        let checksum = cache.save_wasm(CONTRACT).unwrap();
+
+        // Remove compiled module from disk
+        remove_dir_all(options.base_dir.join(CACHE_DIR).join(MODULES_DIR)).unwrap();
+
+        // The first get_instance recompiles the Wasm (miss)
+        let backend = mock_backend(&[]);
+        let _instance = cache
+            .get_instance(&checksum, backend, TESTING_OPTIONS)
+            .unwrap();
+        assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
+        assert_eq!(cache.stats().hits_memory_cache, 0);
+        assert_eq!(cache.stats().hits_fs_cache, 0);
+        assert_eq!(cache.stats().misses, 1);
+
+        // The second get_instance finds the module in cache (hit)
+        let backend = mock_backend(&[]);
+        let _instance = cache
+            .get_instance(&checksum, backend, TESTING_OPTIONS)
+            .unwrap();
+        assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
+        assert_eq!(cache.stats().hits_memory_cache, 1);
+        assert_eq!(cache.stats().hits_fs_cache, 0);
+        assert_eq!(cache.stats().misses, 1);
+    }
+
     #[test]
     fn call_instantiate_on_cached_contract() {
         let cache = unsafe { Cache::new(make_testing_options()).unwrap() };
@@ -1238,6 +1269,34 @@ mod tests {
         cache.unpin(&non_id).unwrap();
     }
 
+    #[test]
+    fn pin_recompiles_module() {
+        let options = make_testing_options();
+        let cache: Cache<MockApi, MockStorage, MockQuerier> =
+            unsafe { Cache::new(options.clone()).unwrap() };
+        let checksum = cache.save_wasm(CONTRACT).unwrap();
+
+        // Remove compiled module from disk
+        remove_dir_all(options.base_dir.join(CACHE_DIR).join(MODULES_DIR)).unwrap();
+
+        // Pin misses, forcing a re-compile of the module
+        cache.pin(&checksum).unwrap();
+        assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
+        assert_eq!(cache.stats().hits_memory_cache, 0);
+        assert_eq!(cache.stats().hits_fs_cache, 0);
+        assert_eq!(cache.stats().misses, 1);
+
+        // After the compilation in pin, the module can be used from pinned memory cache
+        let backend = mock_backend(&[]);
+        let _ = cache
+            .get_instance(&checksum, backend, TESTING_OPTIONS)
+            .unwrap();
+        assert_eq!(cache.stats().hits_pinned_memory_cache, 1);
+        assert_eq!(cache.stats().hits_memory_cache, 0);
+        assert_eq!(cache.stats().hits_fs_cache, 0);
+        assert_eq!(cache.stats().misses, 1);
+    }
+
     #[test]
     fn loading_without_extension_works() {
         let tmp_dir = TempDir::new().unwrap();
diff --git a/packages/vm/src/compatibility.rs b/packages/vm/src/compatibility.rs
index a5284a6e2..0e399e086 100644
--- a/packages/vm/src/compatibility.rs
+++ b/packages/vm/src/compatibility.rs
@@ -260,6 +260,7 @@ mod tests {
     static CONTRACT_0_14: &[u8] = include_bytes!("../testdata/hackatom_0.14.wasm");
     static CONTRACT_0_15: &[u8] = include_bytes!("../testdata/hackatom_0.15.wasm");
     static CONTRACT: &[u8] = include_bytes!("../testdata/hackatom.wasm");
+    static CONTRACT_RUST_170: &[u8] = include_bytes!("../testdata/cyberpunk_rust170.wasm");
 
     fn default_capabilities() -> HashSet<String> {
         ["staking".to_string()].into_iter().collect()
@@ -271,6 +272,12 @@ mod tests {
         check_wasm(CONTRACT, &default_capabilities()).unwrap();
     }
 
+    #[test]
+    fn check_wasm_allows_sign_ext() {
+        // See https://github.com/CosmWasm/cosmwasm/issues/1727
+        check_wasm(CONTRACT_RUST_170, &default_capabilities()).unwrap();
+    }
+
     #[test]
     fn check_wasm_old_contract() {
         match check_wasm(CONTRACT_0_15, &default_capabilities()) {
diff --git a/packages/vm/src/modules/file_system_cache.rs b/packages/vm/src/modules/file_system_cache.rs
index f2298c8f6..d3c967941 100644
--- a/packages/vm/src/modules/file_system_cache.rs
+++ b/packages/vm/src/modules/file_system_cache.rs
@@ -1,8 +1,7 @@
 use std::fs;
 use std::hash::Hash;
 use std::io;
-use std::path::Path;
-use std::path::PathBuf;
+use std::path::{Path, PathBuf};
 use thiserror::Error;
 
 use wasmer::{AsEngineRef, DeserializeError, Module, Target};
@@ -45,7 +44,9 @@ use crate::modules::current_wasmer_module_version;
 ///   A change in memory layout of some types in Rust [std] caused
 ///   [issues with module deserialization](https://github.com/CosmWasm/wasmvm/issues/426).
 ///   To work around this, the version was bumped to "v5" here to invalidate these corrupt caches.
-const MODULE_SERIALIZATION_VERSION: &str = "v5";
+/// - **v6**:<br>
+///   Version for cosmwasm_vm 1.3+ which adds a sub-folder with the target identier for the modules.
+const MODULE_SERIALIZATION_VERSION: &str = "v6";
 
 /// Representation of a directory that contains compiled Wasm artifacts.
 pub struct FileSystemCache {
@@ -279,7 +280,7 @@ mod tests {
         cache.store(&checksum, &module).unwrap();
 
         let mut globber = glob::glob(&format!(
-            "{}/v5-wasmer4/**/{}",
+            "{}/v6-wasmer4/**/{}",
             tmp_dir.path().to_string_lossy(),
             checksum
         ))
@@ -357,9 +358,9 @@ mod tests {
         assert_eq!(
             p.as_os_str(),
             if cfg!(windows) {
-                "modules\\v5-wasmer17\\x86_64-nintendo-fuchsia-gnu-coff-01E9F9FE"
+                "modules\\v6-wasmer17\\x86_64-nintendo-fuchsia-gnu-coff-01E9F9FE"
             } else {
-                "modules/v5-wasmer17/x86_64-nintendo-fuchsia-gnu-coff-01E9F9FE"
+                "modules/v6-wasmer17/x86_64-nintendo-fuchsia-gnu-coff-01E9F9FE"
             }
         );
     }
diff --git a/packages/vm/src/wasm_backend/gatekeeper.rs b/packages/vm/src/wasm_backend/gatekeeper.rs
index 3062e3950..bc9c770c9 100644
--- a/packages/vm/src/wasm_backend/gatekeeper.rs
+++ b/packages/vm/src/wasm_backend/gatekeeper.rs
@@ -198,13 +198,17 @@ impl FunctionMiddleware for FunctionGatekeeper {
             | Operator::I64Rotl
             | Operator::I64Rotr
             | Operator::I32WrapI64
+            // Those are part of the MVP
+            // https://github.com/bytecodealliance/wasm-tools/blob/wasmparser-0.107.0/crates/wasmparser/src/lib.rs#L287-L288
+            | Operator::I64ExtendI32S
+            | Operator::I64ExtendI32U
+            // Sign-extension
+            // https://github.com/bytecodealliance/wasm-tools/blob/wasmparser-0.107.0/crates/wasmparser/src/lib.rs#L307-L311
             | Operator::I32Extend8S
             | Operator::I32Extend16S
             | Operator::I64Extend8S
             | Operator::I64Extend16S
-            | Operator::I64ExtendI32S
-            | Operator::I64Extend32S
-            | Operator::I64ExtendI32U => {
+            | Operator::I64Extend32S => {
                 state.push_operator(operator);
                 Ok(())
             }


Test patch:
diff --git a/packages/std/src/testing/mock.rs b/packages/std/src/testing/mock.rs
index 375d0c86d..69d3281dd 100644
--- a/packages/std/src/testing/mock.rs
+++ b/packages/std/src/testing/mock.rs
@@ -28,6 +28,8 @@ use crate::query::{
     AllDelegationsResponse, AllValidatorsResponse, BondedDenomResponse, DelegationResponse,
     FullDelegation, StakingQuery, Validator, ValidatorResponse,
 };
+#[cfg(feature = "cosmwasm_1_3")]
+use crate::query::{DelegatorWithdrawAddressResponse, DistributionQuery};
 use crate::results::{ContractResult, Empty, SystemResult};
 use crate::serde::{from_slice, to_binary};
 use crate::storage::MemoryStorage;
@@ -443,6 +445,8 @@ pub struct MockQuerier<C: DeserializeOwned = Empty> {
     bank: BankQuerier,
     #[cfg(feature = "staking")]
     staking: StakingQuerier,
+    #[cfg(feature = "cosmwasm_1_3")]
+    distribution: DistributionQuerier,
     wasm: WasmQuerier,
     #[cfg(feature = "stargate")]
     ibc: IbcQuerier,
@@ -457,6 +461,8 @@ impl<C: DeserializeOwned> MockQuerier<C> {
     pub fn new(balances: &[(&str, &[Coin])]) -> Self {
         MockQuerier {
             bank: BankQuerier::new(balances),
+            #[cfg(feature = "cosmwasm_1_3")]
+            distribution: DistributionQuerier::default(),
             #[cfg(feature = "staking")]
             staking: StakingQuerier::default(),
             wasm: WasmQuerier::default(),
@@ -484,6 +490,33 @@ impl<C: DeserializeOwned> MockQuerier<C> {
         self.bank.set_denom_metadata(denom_metadata);
     }
 
+    #[cfg(feature = "cosmwasm_1_3")]
+    pub fn set_withdraw_address(
+        &mut self,
+        delegator_address: impl Into<String>,
+        withdraw_address: impl Into<String>,
+    ) {
+        self.distribution
+            .set_withdraw_address(delegator_address, withdraw_address);
+    }
+
+    /// Sets multiple withdraw addresses.
+    ///
+    /// This allows passing multiple tuples of `(delegator_address, withdraw_address)`.
+    /// It does not overwrite existing entries.
+    #[cfg(feature = "cosmwasm_1_3")]
+    pub fn set_withdraw_addresses(
+        &mut self,
+        withdraw_addresses: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
+    ) {
+        self.distribution.set_withdraw_addresses(withdraw_addresses);
+    }
+
+    #[cfg(feature = "cosmwasm_1_3")]
+    pub fn clear_withdraw_addresses(&mut self) {
+        self.distribution.clear_withdraw_addresses();
+    }
+
     #[cfg(feature = "staking")]
     pub fn update_staking(
         &mut self,
@@ -543,6 +576,10 @@ impl<C: CustomQuery + DeserializeOwned> MockQuerier<C> {
             QueryRequest::Custom(custom_query) => (*self.custom_handler)(custom_query),
             #[cfg(feature = "staking")]
             QueryRequest::Staking(staking_query) => self.staking.query(staking_query),
+            #[cfg(feature = "cosmwasm_1_3")]
+            QueryRequest::Distribution(distribution_query) => {
+                self.distribution.query(distribution_query)
+            }
             QueryRequest::Wasm(msg) => self.wasm.query(msg),
             #[cfg(feature = "stargate")]
             QueryRequest::Stargate { .. } => SystemResult::Err(SystemError::UnsupportedRequest {
@@ -893,6 +930,62 @@ impl StakingQuerier {
     }
 }
 
+#[cfg(feature = "cosmwasm_1_3")]
+#[derive(Clone, Default)]
+pub struct DistributionQuerier {
+    withdraw_addresses: HashMap<String, String>,
+}
+
+#[cfg(feature = "cosmwasm_1_3")]
+impl DistributionQuerier {
+    pub fn new(withdraw_addresses: HashMap<String, String>) -> Self {
+        DistributionQuerier { withdraw_addresses }
+    }
+
+    pub fn set_withdraw_address(
+        &mut self,
+        delegator_address: impl Into<String>,
+        withdraw_address: impl Into<String>,
+    ) {
+        self.withdraw_addresses
+            .insert(delegator_address.into(), withdraw_address.into());
+    }
+
+    /// Sets multiple withdraw addresses.
+    ///
+    /// This allows passing multiple tuples of `(delegator_address, withdraw_address)`.
+    /// It does not overwrite existing entries.
+    pub fn set_withdraw_addresses(
+        &mut self,
+        withdraw_addresses: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
+    ) {
+        for (d, w) in withdraw_addresses {
+            self.set_withdraw_address(d, w);
+        }
+    }
+
+    pub fn clear_withdraw_addresses(&mut self) {
+        self.withdraw_addresses.clear();
+    }
+
+    pub fn query(&self, request: &DistributionQuery) -> QuerierResult {
+        let contract_result: ContractResult<Binary> = match request {
+            DistributionQuery::DelegatorWithdrawAddress { delegator_address } => {
+                let res = DelegatorWithdrawAddressResponse {
+                    withdraw_address: self
+                        .withdraw_addresses
+                        .get(delegator_address)
+                        .unwrap_or(delegator_address)
+                        .clone(),
+                };
+                to_binary(&res).into()
+            }
+        };
+        // system result is always ok in the mock implementation
+        SystemResult::Ok(contract_result)
+    }
+}
+
 pub fn digit_sum(input: &[u8]) -> usize {
     input.iter().fold(0, |sum, val| sum + (*val as usize))
 }
@@ -1428,6 +1521,29 @@ mod tests {
         );
     }
 
+    #[cfg(feature = "cosmwasm_1_3")]
+    #[test]
+    fn distribution_querier_delegator_withdraw_address() {
+        let mut distribution = DistributionQuerier::default();
+        distribution.set_withdraw_address("addr0", "withdraw0");
+
+        let query = DistributionQuery::DelegatorWithdrawAddress {
+            delegator_address: "addr0".to_string(),
+        };
+
+        let res = distribution.query(&query).unwrap().unwrap();
+        let res: DelegatorWithdrawAddressResponse = from_binary(&res).unwrap();
+        assert_eq!(res.withdraw_address, "withdraw0");
+
+        let query = DistributionQuery::DelegatorWithdrawAddress {
+            delegator_address: "addr1".to_string(),
+        };
+
+        let res = distribution.query(&query).unwrap().unwrap();
+        let res: DelegatorWithdrawAddressResponse = from_binary(&res).unwrap();
+        assert_eq!(res.withdraw_address, "addr1");
+    }
+
     #[cfg(feature = "stargate")]
     #[test]
     fn ibc_querier_channel_existing() {
diff --git a/packages/std/src/testing/mod.rs b/packages/std/src/testing/mod.rs
index 657aada5d..550486a0b 100644
--- a/packages/std/src/testing/mod.rs
+++ b/packages/std/src/testing/mod.rs
@@ -9,6 +9,8 @@ mod shuffle;
 
 pub use assertions::assert_approx_eq_impl;
 
+#[cfg(feature = "cosmwasm_1_3")]
+pub use mock::DistributionQuerier;
 #[cfg(feature = "staking")]
 pub use mock::StakingQuerier;
 pub use mock::{