Repository: CosmWasm/cosmwasm
Pull request #903 (2021-04-28 13:10:06)

Refactor Timestamps
This primarily fixes the problem that a u64 nanosecond timestamp exceeds the 53 bit safe integer range after 15 weeks (starting at unix epoch).

It also supersedes #902 (closes #902) by changing the Timestamp type to uint64, serialized to string.

Open questions / follow ups:
1. Should we change `BlockInfo.time` to `Timestamp`, such that we only need one field and simplify the conversion from block time to timeout
2. Do we want to serialize the `enum IbcTimeout` to an object with two fields in all 3 cases

Both of those should not block this PR.


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fb79cd7d7..f82c97d1c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -73,6 +73,11 @@ and this project adheres to
   contract (eg. multisig) to clear the admin, to prevent future migrations
   ([#900])
 - cosmwasm-std: Implement `Display for Coin` ([#901]).
+- cosmwasm-std: Create `Uint64` analogously to `Uint128` with string
+  serialization allowing the use of the full uint64 range in JSON clients that
+  use float numbers, such as JavaScript and jq.
+- cosmwasm-std: Create const functions `Uint64::new` and `Uint128::new` to
+  create instances in a const context.
 
 [#692]: https://github.com/CosmWasm/cosmwasm/issues/692
 [#706]: https://github.com/CosmWasm/cosmwasm/pull/706
diff --git a/IBC.md b/IBC.md
index bc91afeca..a700ff0ef 100644
--- a/IBC.md
+++ b/IBC.md
@@ -37,21 +37,24 @@ pub enum IbcMsg {
     }
 }
 
+/// In IBC each package must set at least one type of timeout:
+/// the timestamp or the block height. Using this rather complex enum instead of
+/// two timeout fields we ensure that at least one timeout is set.
 #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
 #[serde(rename_all = "snake_case")]
 pub enum IbcTimeout {
-    /// block timestamp (nanoseconds since UNIX epoch) after which the packet times out
-    /// (measured on the remote chain)
-    /// See https://golang.org/pkg/time/#Time.UnixNano
-    TimestampNanos(u64),
-    /// block after which the packet times out (measured on remote chain)
+    /// Block timestamp (nanoseconds since UNIX epoch) after which the packet times out
+    /// (measured on the remote chain).
+    Timestamp(Timestamp),
+    /// Block after which the packet times out (measured on remote chain).
     Block(IbcTimeoutBlock),
+    /// Use this to set both timestamp and block timeout. The package then times out once
+    /// the first of both timeouts is hit.
     Both {
-        timestamp_nanos: u64,
+        timestamp: Timestamp,
         block: IbcTimeoutBlock,
     },
 }
-
 ```
 
 Note the `to_address` is likely not a valid `Addr`, as it uses the bech32 prefix
diff --git a/contracts/crypto-verify/schema/query_msg.json b/contracts/crypto-verify/schema/query_msg.json
index b97687882..1f2894ff4 100644
--- a/contracts/crypto-verify/schema/query_msg.json
+++ b/contracts/crypto-verify/schema/query_msg.json
@@ -255,6 +255,7 @@
       "type": "string"
     },
     "Uint128": {
+      "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
       "type": "string"
     }
   }
diff --git a/contracts/hackatom/schema/balance_response.json b/contracts/hackatom/schema/balance_response.json
index df071aca0..4e3b16aa4 100644
--- a/contracts/hackatom/schema/balance_response.json
+++ b/contracts/hackatom/schema/balance_response.json
@@ -32,6 +32,7 @@
       }
     },
     "Uint128": {
+      "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
       "type": "string"
     }
   }
diff --git a/contracts/hackatom/schema/sudo_msg.json b/contracts/hackatom/schema/sudo_msg.json
index 0b78b6342..32cea1108 100644
--- a/contracts/hackatom/schema/sudo_msg.json
+++ b/contracts/hackatom/schema/sudo_msg.json
@@ -48,6 +48,7 @@
       }
     },
     "Uint128": {
+      "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
       "type": "string"
     }
   }
diff --git a/contracts/ibc-reflect-send/schema/account_response.json b/contracts/ibc-reflect-send/schema/account_response.json
index 90faddfa1..83009f9c3 100644
--- a/contracts/ibc-reflect-send/schema/account_response.json
+++ b/contracts/ibc-reflect-send/schema/account_response.json
@@ -44,6 +44,7 @@
       }
     },
     "Uint128": {
+      "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
       "type": "string"
     }
   }
diff --git a/contracts/ibc-reflect-send/schema/execute_msg.json b/contracts/ibc-reflect-send/schema/execute_msg.json
index 3d50e283d..8a78e7b62 100644
--- a/contracts/ibc-reflect-send/schema/execute_msg.json
+++ b/contracts/ibc-reflect-send/schema/execute_msg.json
@@ -435,24 +435,23 @@
       ]
     },
     "IbcTimeout": {
+      "description": "In IBC each package must set at least one type of timeout: the timestamp or the block height. Using this rather complex enum instead of two timeout fields we ensure that at least one timeout is set.",
       "anyOf": [
         {
-          "description": "block timestamp (nanoseconds since UNIX epoch) after which the packet times out (measured on the remote chain) See https://golang.org/pkg/time/#Time.UnixNano",
+          "description": "Block timestamp (nanoseconds since UNIX epoch) after which the packet times out (measured on the remote chain).",
           "type": "object",
           "required": [
-            "timestamp_nanos"
+            "timestamp"
           ],
           "properties": {
-            "timestamp_nanos": {
-              "type": "integer",
-              "format": "uint64",
-              "minimum": 0.0
+            "timestamp": {
+              "$ref": "#/definitions/Timestamp"
             }
           },
           "additionalProperties": false
         },
         {
-          "description": "block after which the packet times out (measured on remote chain)",
+          "description": "Block after which the packet times out (measured on remote chain).",
           "type": "object",
           "required": [
             "block"
@@ -465,6 +464,7 @@
           "additionalProperties": false
         },
         {
+          "description": "Use this to set both timestamp and block timeout. The package then times out once the first of both timeouts is hit.",
           "type": "object",
           "required": [
             "both"
@@ -474,16 +474,14 @@
               "type": "object",
               "required": [
                 "block",
-                "timestamp_nanos"
+                "timestamp"
               ],
               "properties": {
                 "block": {
                   "$ref": "#/definitions/IbcTimeoutBlock"
                 },
-                "timestamp_nanos": {
-                  "type": "integer",
-                  "format": "uint64",
-                  "minimum": 0.0
+                "timestamp": {
+                  "$ref": "#/definitions/Timestamp"
                 }
               }
             }
@@ -598,7 +596,20 @@
         }
       ]
     },
+    "Timestamp": {
+      "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.",
+      "allOf": [
+        {
+          "$ref": "#/definitions/Uint64"
+        }
+      ]
+    },
     "Uint128": {
+      "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
+      "type": "string"
+    },
+    "Uint64": {
+      "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```",
       "type": "string"
     },
     "WasmMsg": {
diff --git a/contracts/ibc-reflect-send/schema/list_accounts_response.json b/contracts/ibc-reflect-send/schema/list_accounts_response.json
index a229e91f3..ec9921118 100644
--- a/contracts/ibc-reflect-send/schema/list_accounts_response.json
+++ b/contracts/ibc-reflect-send/schema/list_accounts_response.json
@@ -62,6 +62,7 @@
       }
     },
     "Uint128": {
+      "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
       "type": "string"
     }
   }
diff --git a/contracts/ibc-reflect-send/schema/packet_msg.json b/contracts/ibc-reflect-send/schema/packet_msg.json
index 89930b04b..502f680ad 100644
--- a/contracts/ibc-reflect-send/schema/packet_msg.json
+++ b/contracts/ibc-reflect-send/schema/packet_msg.json
@@ -388,24 +388,23 @@
       ]
     },
     "IbcTimeout": {
+      "description": "In IBC each package must set at least one type of timeout: the timestamp or the block height. Using this rather complex enum instead of two timeout fields we ensure that at least one timeout is set.",
       "anyOf": [
         {
-          "description": "block timestamp (nanoseconds since UNIX epoch) after which the packet times out (measured on the remote chain) See https://golang.org/pkg/time/#Time.UnixNano",
+          "description": "Block timestamp (nanoseconds since UNIX epoch) after which the packet times out (measured on the remote chain).",
           "type": "object",
           "required": [
-            "timestamp_nanos"
+            "timestamp"
           ],
           "properties": {
-            "timestamp_nanos": {
-              "type": "integer",
-              "format": "uint64",
-              "minimum": 0.0
+            "timestamp": {
+              "$ref": "#/definitions/Timestamp"
             }
           },
           "additionalProperties": false
         },
         {
-          "description": "block after which the packet times out (measured on remote chain)",
+          "description": "Block after which the packet times out (measured on remote chain).",
           "type": "object",
           "required": [
             "block"
@@ -418,6 +417,7 @@
           "additionalProperties": false
         },
         {
+          "description": "Use this to set both timestamp and block timeout. The package then times out once the first of both timeouts is hit.",
           "type": "object",
           "required": [
             "both"
@@ -427,16 +427,14 @@
               "type": "object",
               "required": [
                 "block",
-                "timestamp_nanos"
+                "timestamp"
               ],
               "properties": {
                 "block": {
                   "$ref": "#/definitions/IbcTimeoutBlock"
                 },
-                "timestamp_nanos": {
-                  "type": "integer",
-                  "format": "uint64",
-                  "minimum": 0.0
+                "timestamp": {
+                  "$ref": "#/definitions/Timestamp"
                 }
               }
             }
@@ -551,7 +549,20 @@
         }
       ]
     },
+    "Timestamp": {
+      "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.",
+      "allOf": [
+        {
+          "$ref": "#/definitions/Uint64"
+        }
+      ]
+    },
     "Uint128": {
+      "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
+      "type": "string"
+    },
+    "Uint64": {
+      "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```",
       "type": "string"
     },
     "WasmMsg": {
diff --git a/contracts/ibc-reflect-send/src/ibc.rs b/contracts/ibc-reflect-send/src/ibc.rs
index 6fa0faafd..615ecdc4a 100644
--- a/contracts/ibc-reflect-send/src/ibc.rs
+++ b/contracts/ibc-reflect-send/src/ibc.rs
@@ -445,7 +445,7 @@ mod tests {
                 assert_eq!(transfer_channel_id, channel_id.as_str());
                 assert_eq!(remote_addr, to_address.as_str());
                 assert_eq!(&coin(12344, "utrgd"), amount);
-                assert!(matches!(timeout, IbcTimeout::TimestampNanos { .. }));
+                assert!(matches!(timeout, IbcTimeout::Timestamp { .. }));
             }
             o => panic!("unexpected message: {:?}", o),
         }
diff --git a/contracts/ibc-reflect/schema/acknowledgement_msg_balances.json b/contracts/ibc-reflect/schema/acknowledgement_msg_balances.json
index 198e96e57..3b61ef769 100644
--- a/contracts/ibc-reflect/schema/acknowledgement_msg_balances.json
+++ b/contracts/ibc-reflect/schema/acknowledgement_msg_balances.json
@@ -65,6 +65,7 @@
       }
     },
     "Uint128": {
+      "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
       "type": "string"
     }
   }
diff --git a/contracts/ibc-reflect/schema/packet_msg.json b/contracts/ibc-reflect/schema/packet_msg.json
index 5817c3960..a16d8cb3f 100644
--- a/contracts/ibc-reflect/schema/packet_msg.json
+++ b/contracts/ibc-reflect/schema/packet_msg.json
@@ -387,24 +387,23 @@
       ]
     },
     "IbcTimeout": {
+      "description": "In IBC each package must set at least one type of timeout: the timestamp or the block height. Using this rather complex enum instead of two timeout fields we ensure that at least one timeout is set.",
       "anyOf": [
         {
-          "description": "block timestamp (nanoseconds since UNIX epoch) after which the packet times out (measured on the remote chain) See https://golang.org/pkg/time/#Time.UnixNano",
+          "description": "Block timestamp (nanoseconds since UNIX epoch) after which the packet times out (measured on the remote chain).",
           "type": "object",
           "required": [
-            "timestamp_nanos"
+            "timestamp"
           ],
           "properties": {
-            "timestamp_nanos": {
-              "type": "integer",
-              "format": "uint64",
-              "minimum": 0.0
+            "timestamp": {
+              "$ref": "#/definitions/Timestamp"
             }
           },
           "additionalProperties": false
         },
         {
-          "description": "block after which the packet times out (measured on remote chain)",
+          "description": "Block after which the packet times out (measured on remote chain).",
           "type": "object",
           "required": [
             "block"
@@ -417,6 +416,7 @@
           "additionalProperties": false
         },
         {
+          "description": "Use this to set both timestamp and block timeout. The package then times out once the first of both timeouts is hit.",
           "type": "object",
           "required": [
             "both"
@@ -426,16 +426,14 @@
               "type": "object",
               "required": [
                 "block",
-                "timestamp_nanos"
+                "timestamp"
               ],
               "properties": {
                 "block": {
                   "$ref": "#/definitions/IbcTimeoutBlock"
                 },
-                "timestamp_nanos": {
-                  "type": "integer",
-                  "format": "uint64",
-                  "minimum": 0.0
+                "timestamp": {
+                  "$ref": "#/definitions/Timestamp"
                 }
               }
             }
@@ -550,7 +548,20 @@
         }
       ]
     },
+    "Timestamp": {
+      "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.",
+      "allOf": [
+        {
+          "$ref": "#/definitions/Uint64"
+        }
+      ]
+    },
     "Uint128": {
+      "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
+      "type": "string"
+    },
+    "Uint64": {
+      "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```",
       "type": "string"
     },
     "WasmMsg": {
diff --git a/contracts/reflect/schema/execute_msg.json b/contracts/reflect/schema/execute_msg.json
index 7853824fd..6e2cc1611 100644
--- a/contracts/reflect/schema/execute_msg.json
+++ b/contracts/reflect/schema/execute_msg.json
@@ -431,24 +431,23 @@
       ]
     },
     "IbcTimeout": {
+      "description": "In IBC each package must set at least one type of timeout: the timestamp or the block height. Using this rather complex enum instead of two timeout fields we ensure that at least one timeout is set.",
       "anyOf": [
         {
-          "description": "block timestamp (nanoseconds since UNIX epoch) after which the packet times out (measured on the remote chain) See https://golang.org/pkg/time/#Time.UnixNano",
+          "description": "Block timestamp (nanoseconds since UNIX epoch) after which the packet times out (measured on the remote chain).",
           "type": "object",
           "required": [
-            "timestamp_nanos"
+            "timestamp"
           ],
           "properties": {
-            "timestamp_nanos": {
-              "type": "integer",
-              "format": "uint64",
-              "minimum": 0.0
+            "timestamp": {
+              "$ref": "#/definitions/Timestamp"
             }
           },
           "additionalProperties": false
         },
         {
-          "description": "block after which the packet times out (measured on remote chain)",
+          "description": "Block after which the packet times out (measured on remote chain).",
           "type": "object",
           "required": [
             "block"
@@ -461,6 +460,7 @@
           "additionalProperties": false
         },
         {
+          "description": "Use this to set both timestamp and block timeout. The package then times out once the first of both timeouts is hit.",
           "type": "object",
           "required": [
             "both"
@@ -470,16 +470,14 @@
               "type": "object",
               "required": [
                 "block",
-                "timestamp_nanos"
+                "timestamp"
               ],
               "properties": {
                 "block": {
                   "$ref": "#/definitions/IbcTimeoutBlock"
                 },
-                "timestamp_nanos": {
-                  "type": "integer",
-                  "format": "uint64",
-                  "minimum": 0.0
+                "timestamp": {
+                  "$ref": "#/definitions/Timestamp"
                 }
               }
             }
@@ -633,7 +631,20 @@
         }
       }
     },
+    "Timestamp": {
+      "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.",
+      "allOf": [
+        {
+          "$ref": "#/definitions/Uint64"
+        }
+      ]
+    },
     "Uint128": {
+      "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
+      "type": "string"
+    },
+    "Uint64": {
+      "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```",
       "type": "string"
     },
     "WasmMsg": {
diff --git a/contracts/reflect/schema/response_for__custom_msg.json b/contracts/reflect/schema/response_for__custom_msg.json
index 35cb5dcd7..3fffad4ac 100644
--- a/contracts/reflect/schema/response_for__custom_msg.json
+++ b/contracts/reflect/schema/response_for__custom_msg.json
@@ -419,24 +419,23 @@
       ]
     },
     "IbcTimeout": {
+      "description": "In IBC each package must set at least one type of timeout: the timestamp or the block height. Using this rather complex enum instead of two timeout fields we ensure that at least one timeout is set.",
       "anyOf": [
         {
-          "description": "block timestamp (nanoseconds since UNIX epoch) after which the packet times out (measured on the remote chain) See https://golang.org/pkg/time/#Time.UnixNano",
+          "description": "Block timestamp (nanoseconds since UNIX epoch) after which the packet times out (measured on the remote chain).",
           "type": "object",
           "required": [
-            "timestamp_nanos"
+            "timestamp"
           ],
           "properties": {
-            "timestamp_nanos": {
-              "type": "integer",
-              "format": "uint64",
-              "minimum": 0.0
+            "timestamp": {
+              "$ref": "#/definitions/Timestamp"
             }
           },
           "additionalProperties": false
         },
         {
-          "description": "block after which the packet times out (measured on remote chain)",
+          "description": "Block after which the packet times out (measured on remote chain).",
           "type": "object",
           "required": [
             "block"
@@ -449,6 +448,7 @@
           "additionalProperties": false
         },
         {
+          "description": "Use this to set both timestamp and block timeout. The package then times out once the first of both timeouts is hit.",
           "type": "object",
           "required": [
             "both"
@@ -458,16 +458,14 @@
               "type": "object",
               "required": [
                 "block",
-                "timestamp_nanos"
+                "timestamp"
               ],
               "properties": {
                 "block": {
                   "$ref": "#/definitions/IbcTimeoutBlock"
                 },
-                "timestamp_nanos": {
-                  "type": "integer",
-                  "format": "uint64",
-                  "minimum": 0.0
+                "timestamp": {
+                  "$ref": "#/definitions/Timestamp"
                 }
               }
             }
@@ -621,7 +619,20 @@
         }
       }
     },
+    "Timestamp": {
+      "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.",
+      "allOf": [
+        {
+          "$ref": "#/definitions/Uint64"
+        }
+      ]
+    },
     "Uint128": {
+      "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
+      "type": "string"
+    },
+    "Uint64": {
+      "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```",
       "type": "string"
     },
     "WasmMsg": {
diff --git a/contracts/staking/schema/balance_response.json b/contracts/staking/schema/balance_response.json
index efa5d6ce0..4e1a0be2b 100644
--- a/contracts/staking/schema/balance_response.json
+++ b/contracts/staking/schema/balance_response.json
@@ -12,6 +12,7 @@
   },
   "definitions": {
     "Uint128": {
+      "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
       "type": "string"
     }
   }
diff --git a/contracts/staking/schema/claims_response.json b/contracts/staking/schema/claims_response.json
index 142db93da..a072a4ce7 100644
--- a/contracts/staking/schema/claims_response.json
+++ b/contracts/staking/schema/claims_response.json
@@ -12,6 +12,7 @@
   },
   "definitions": {
     "Uint128": {
+      "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
       "type": "string"
     }
   }
diff --git a/contracts/staking/schema/execute_msg.json b/contracts/staking/schema/execute_msg.json
index 2aad3c31e..3f24669d5 100644
--- a/contracts/staking/schema/execute_msg.json
+++ b/contracts/staking/schema/execute_msg.json
@@ -103,6 +103,7 @@
   ],
   "definitions": {
     "Uint128": {
+      "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
       "type": "string"
     }
   }
diff --git a/contracts/staking/schema/instantiate_msg.json b/contracts/staking/schema/instantiate_msg.json
index 361879b51..e17d7a05e 100644
--- a/contracts/staking/schema/instantiate_msg.json
+++ b/contracts/staking/schema/instantiate_msg.json
@@ -52,6 +52,7 @@
       "type": "string"
     },
     "Uint128": {
+      "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
       "type": "string"
     }
   }
diff --git a/contracts/staking/schema/investment_info.json b/contracts/staking/schema/investment_info.json
index 1716f35b8..73f6e3004 100644
--- a/contracts/staking/schema/investment_info.json
+++ b/contracts/staking/schema/investment_info.json
@@ -54,6 +54,7 @@
       "type": "string"
     },
     "Uint128": {
+      "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
       "type": "string"
     }
   }
diff --git a/contracts/staking/schema/investment_response.json b/contracts/staking/schema/investment_response.json
index ce1a140b6..88a62af43 100644
--- a/contracts/staking/schema/investment_response.json
+++ b/contracts/staking/schema/investment_response.json
@@ -67,6 +67,7 @@
       "type": "string"
     },
     "Uint128": {
+      "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
       "type": "string"
     }
   }
diff --git a/contracts/staking/schema/supply.json b/contracts/staking/schema/supply.json
index 0c5af2696..08d0c2b7a 100644
--- a/contracts/staking/schema/supply.json
+++ b/contracts/staking/schema/supply.json
@@ -36,6 +36,7 @@
   },
   "definitions": {
     "Uint128": {
+      "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
       "type": "string"
     }
   }
diff --git a/packages/std/examples/schema.rs b/packages/std/examples/schema.rs
index ff23a86fe..a698b2a8f 100644
--- a/packages/std/examples/schema.rs
+++ b/packages/std/examples/schema.rs
@@ -1,8 +1,8 @@
 use std::env::current_dir;
 use std::fs::create_dir_all;
 
-use cosmwasm_schema::{export_schema_with_title, remove_schemas, schema_for};
-use cosmwasm_std::CosmosMsg;
+use cosmwasm_schema::{export_schema, export_schema_with_title, remove_schemas, schema_for};
+use cosmwasm_std::{CosmosMsg, Timestamp};
 
 fn main() {
     let mut out_dir = current_dir().unwrap();
@@ -10,5 +10,6 @@ fn main() {
     create_dir_all(&out_dir).unwrap();
     remove_schemas(&out_dir).unwrap();
 
+    export_schema(&schema_for!(Timestamp), &out_dir);
     export_schema_with_title(&mut schema_for!(CosmosMsg), &out_dir, "CosmosMsg");
 }
diff --git a/packages/std/schema/cosmos_msg.json b/packages/std/schema/cosmos_msg.json
index 7f171da69..c1bd30038 100644
--- a/packages/std/schema/cosmos_msg.json
+++ b/packages/std/schema/cosmos_msg.json
@@ -121,6 +121,7 @@
       "type": "object"
     },
     "Uint128": {
+      "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
       "type": "string"
     },
     "WasmMsg": {
diff --git a/packages/std/schema/timestamp.json b/packages/std/schema/timestamp.json
new file mode 100644
index 000000000..889461d98
--- /dev/null
+++ b/packages/std/schema/timestamp.json
@@ -0,0 +1,16 @@
+{
+  "$schema": "http://json-schema.org/draft-07/schema#",
+  "title": "Timestamp",
+  "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.",
+  "allOf": [
+    {
+      "$ref": "#/definitions/Uint64"
+    }
+  ],
+  "definitions": {
+    "Uint64": {
+      "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```",
+      "type": "string"
+    }
+  }
+}
diff --git a/packages/std/src/ibc.rs b/packages/std/src/ibc.rs
index cc3b84004..147812cc2 100644
--- a/packages/std/src/ibc.rs
+++ b/packages/std/src/ibc.rs
@@ -54,24 +54,28 @@ pub struct IbcEndpoint {
     pub channel_id: String,
 }
 
+/// In IBC each package must set at least one type of timeout:
+/// the timestamp or the block height. Using this rather complex enum instead of
+/// two timeout fields we ensure that at least one timeout is set.
 #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
 #[serde(rename_all = "snake_case")]
 pub enum IbcTimeout {
-    /// block timestamp (nanoseconds since UNIX epoch) after which the packet times out
-    /// (measured on the remote chain)
-    /// See https://golang.org/pkg/time/#Time.UnixNano
-    TimestampNanos(u64),
-    /// block after which the packet times out (measured on remote chain)
+    /// Block timestamp (nanoseconds since UNIX epoch) after which the packet times out
+    /// (measured on the remote chain).
+    Timestamp(Timestamp),
+    /// Block after which the packet times out (measured on remote chain).
     Block(IbcTimeoutBlock),
+    /// Use this to set both timestamp and block timeout. The package then times out once
+    /// the first of both timeouts is hit.
     Both {
-        timestamp_nanos: u64,
+        timestamp: Timestamp,
         block: IbcTimeoutBlock,
     },
 }
 
 impl From<Timestamp> for IbcTimeout {
-    fn from(time: Timestamp) -> IbcTimeout {
-        IbcTimeout::TimestampNanos(time.seconds * 1_000_000_000 + time.nanos)
+    fn from(timestamp: Timestamp) -> IbcTimeout {
+        IbcTimeout::Timestamp(timestamp)
     }
 }
 
@@ -253,10 +257,10 @@ mod tests {
             channel_id: "channel-123".to_string(),
             to_address: "my-special-addr".into(),
             amount: Coin::new(12345678, "uatom"),
-            timeout: IbcTimeout::TimestampNanos(1234567890),
+            timeout: IbcTimeout::Timestamp(Timestamp::from_nanos(1234567890)),
         };
         let encoded = to_string(&msg).unwrap();
-        let expected = r#"{"transfer":{"channel_id":"channel-123","to_address":"my-special-addr","amount":{"denom":"uatom","amount":"12345678"},"timeout":{"timestamp_nanos":1234567890}}}"#;
+        let expected = r#"{"transfer":{"channel_id":"channel-123","to_address":"my-special-addr","amount":{"denom":"uatom","amount":"12345678"},"timeout":{"timestamp":"1234567890"}}}"#;
         assert_eq!(encoded.as_str(), expected);
     }
 
diff --git a/packages/std/src/lib.rs b/packages/std/src/lib.rs
index f03c6a5f4..64ee45cbb 100644
--- a/packages/std/src/lib.rs
+++ b/packages/std/src/lib.rs
@@ -40,7 +40,7 @@ pub use crate::ibc::{
 #[cfg(feature = "iterator")]
 #[allow(deprecated)]
 pub use crate::iterator::{Order, Pair, KV};
-pub use crate::math::{Decimal, Fraction, Uint128};
+pub use crate::math::{Decimal, Fraction, Uint128, Uint64};
 pub use crate::query::{
     AllBalanceResponse, BalanceResponse, BankQuery, CustomQuery, QueryRequest, WasmQuery,
 };
diff --git a/packages/std/src/math/mod.rs b/packages/std/src/math/mod.rs
index 9843cea18..e9c5dbe28 100644
--- a/packages/std/src/math/mod.rs
+++ b/packages/std/src/math/mod.rs
@@ -1,7 +1,9 @@
 mod decimal;
 mod fraction;
 mod uint128;
+mod uint64;
 
 pub use decimal::Decimal;
 pub use fraction::Fraction;
 pub use uint128::Uint128;
+pub use uint64::Uint64;
diff --git a/packages/std/src/math/uint128.rs b/packages/std/src/math/uint128.rs
index bbf83406b..6d9899ab0 100644
--- a/packages/std/src/math/uint128.rs
+++ b/packages/std/src/math/uint128.rs
@@ -7,11 +7,38 @@ use std::ops;
 
 use crate::errors::{DivideByZeroError, OverflowError, OverflowOperation, StdError};
 
-//*** Uint128 ***/
+/// A thin wrapper around u128 that is using strings for JSON encoding/decoding,
+/// such that the full u128 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 `u128` to get the value out:
+///
+/// ```
+/// # use cosmwasm_std::Uint128;
+/// let a = Uint128::from(123u128);
+/// assert_eq!(a.u128(), 123);
+///
+/// let b = Uint128::from(42u64);
+/// assert_eq!(b.u128(), 42);
+///
+/// let c = Uint128::from(70u32);
+/// assert_eq!(c.u128(), 70);
+/// ```
 #[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, JsonSchema)]
-pub struct Uint128(#[schemars(with = "String")] pub u128);
+pub struct Uint128(
+    #[schemars(with = "String")] pub u128, // Simon thinks this should be private, but does not want to worry about breaking code right now
+);
 
 impl Uint128 {
+    /// Creates a Uint128(value).
+    ///
+    /// This method is less flexible than `from` but can be called in a const context.
+    pub const fn new(value: u128) -> Self {
+        Uint128(value)
+    }
+
     /// Creates a Uint128(0)
     pub const fn zero() -> Self {
         Uint128(0)
@@ -142,7 +169,7 @@ impl TryFrom<&str> for Uint128 {
     fn try_from(val: &str) -> Result<Self, Self::Error> {
         match val.parse::<u128>() {
             Ok(u) => Ok(Uint128(u)),
-            Err(e) => Err(StdError::generic_err(format!("Parsing coin: {}", e))),
+            Err(e) => Err(StdError::generic_err(format!("Parsing u128: {}", e))),
         }
     }
 }
@@ -211,8 +238,8 @@ impl Uint128 {
     }
 }
 
-/// Serializes as a base64 string
 impl Serialize for Uint128 {
+    /// Serializes as an integer string using base 10
     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
     where
         S: ser::Serializer,
@@ -221,8 +248,8 @@ impl Serialize for Uint128 {
     }
 }
 
-/// Deserializes as a base64 string
 impl<'de> Deserialize<'de> for Uint128 {
+    /// Deserialized from an integer string using base 10
     fn deserialize<D>(deserializer: D) -> Result<Uint128, D::Error>
     where
         D: Deserializer<'de>,
@@ -380,7 +407,7 @@ mod tests {
     }
 
     #[test]
-    fn u128_multiply_ratio_works() {
+    fn uint128_multiply_ratio_works() {
         let base = Uint128(500);
 
         // factor 1/1
@@ -403,7 +430,7 @@ mod tests {
 
     #[test]
     #[should_panic(expected = "Denominator must not be zero")]
-    fn u128_multiply_ratio_panics_for_zero_denominator() {
+    fn uint128_multiply_ratio_panics_for_zero_denominator() {
         Uint128(500).multiply_ratio(1u128, 0u128);
     }
 
diff --git a/packages/std/src/math/uint64.rs b/packages/std/src/math/uint64.rs
new file mode 100644
index 000000000..4d8db1065
--- /dev/null
+++ b/packages/std/src/math/uint64.rs
@@ -0,0 +1,478 @@
+use schemars::JsonSchema;
+use serde::{de, ser, Deserialize, Deserializer, Serialize};
+use std::convert::TryFrom;
+use std::fmt::{self};
+use std::iter::Sum;
+use std::ops;
+
+use crate::errors::{DivideByZeroError, OverflowError, OverflowOperation, StdError};
+
+/// A thin wrapper around u64 that is using strings for JSON encoding/decoding,
+/// such that the full u64 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 `u64` to get the value out:
+///
+/// ```
+/// # use cosmwasm_std::Uint64;
+/// let a = Uint64::from(42u64);
+/// assert_eq!(a.u64(), 42);
+///
+/// let b = Uint64::from(70u32);
+/// assert_eq!(b.u64(), 70);
+/// ```
+#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, JsonSchema)]
+pub struct Uint64(#[schemars(with = "String")] u64);
+
+impl Uint64 {
+    /// Creates a Uint64(value).
+    ///
+    /// This method is less flexible than `from` but can be called in a const context.
+    pub const fn new(value: u64) -> Self {
+        Uint64(value)
+    }
+
+    /// Creates a Uint64(0)
+    pub const fn zero() -> Self {
+        Uint64(0)
+    }
+
+    /// Returns a copy of the internal data
+    pub const fn u64(&self) -> u64 {
+        self.0
+    }
+
+    pub fn is_zero(&self) -> bool {
+        self.0 == 0
+    }
+
+    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_div(self, other: Self) -> Result<Self, DivideByZeroError> {
+        self.0
+            .checked_div(other.0)
+            .map(Self)
+            .ok_or_else(|| DivideByZeroError::new(self))
+    }
+
+    pub fn checked_div_euclid(self, other: Self) -> Result<Self, DivideByZeroError> {
+        self.0
+            .checked_div_euclid(other.0)
+            .map(Self)
+            .ok_or_else(|| DivideByZeroError::new(self))
+    }
+
+    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 wrapping_add(self, other: Self) -> Self {
+        Self(self.0.wrapping_add(other.0))
+    }
+
+    pub fn wrapping_sub(self, other: Self) -> Self {
+        Self(self.0.wrapping_sub(other.0))
+    }
+
+    pub fn wrapping_mul(self, other: Self) -> Self {
+        Self(self.0.wrapping_mul(other.0))
+    }
+
+    pub fn wrapping_pow(self, other: u32) -> Self {
+        Self(self.0.wrapping_pow(other))
+    }
+
+    pub fn saturating_add(self, other: Self) -> Self {
+        Self(self.0.saturating_add(other.0))
+    }
+
+    pub fn saturating_sub(self, other: Self) -> Self {
+        Self(self.0.saturating_sub(other.0))
+    }
+
+    pub fn saturating_mul(self, other: Self) -> Self {
+        Self(self.0.saturating_mul(other.0))
+    }
+
+    pub fn saturating_pow(self, other: u32) -> Self {
+        Self(self.0.saturating_pow(other))
+    }
+}
+
+// `From<u{128,64,32,16,8}>` is implemented manually instead of
+// using `impl<T: Into<u64>> From<T> for Uint64` because
+// of the conflict with `TryFrom<&str>` as described here
+// https://stackoverflow.com/questions/63136970/how-do-i-work-around-the-upstream-crates-may-add-a-new-impl-of-trait-error
+
+impl From<u64> for Uint64 {
+    fn from(val: u64) -> Self {
+        Uint64(val)
+    }
+}
+
+impl From<u32> for Uint64 {
+    fn from(val: u32) -> Self {
+        Uint64(val.into())
+    }
+}
+
+impl From<u16> for Uint64 {
+    fn from(val: u16) -> Self {
+        Uint64(val.into())
+    }
+}
+
+impl From<u8> for Uint64 {
+    fn from(val: u8) -> Self {
+        Uint64(val.into())
+    }
+}
+
+impl TryFrom<&str> for Uint64 {
+    type Error = StdError;
+
+    fn try_from(val: &str) -> Result<Self, Self::Error> {
+        match val.parse::<u64>() {
+            Ok(u) => Ok(Uint64(u)),
+            Err(e) => Err(StdError::generic_err(format!("Parsing u64: {}", e))),
+        }
+    }
+}
+
+impl From<Uint64> for String {
+    fn from(original: Uint64) -> Self {
+        original.to_string()
+    }
+}
+
+impl From<Uint64> for u64 {
+    fn from(original: Uint64) -> Self {
+        original.0
+    }
+}
+
+impl fmt::Display for Uint64 {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "{}", self.0)
+    }
+}
+
+impl ops::Add<Uint64> for Uint64 {
+    type Output = Self;
+
+    fn add(self, rhs: Self) -> Self {
+        Uint64(self.u64().checked_add(rhs.u64()).unwrap())
+    }
+}
+
+impl<'a> ops::Add<&'a Uint64> for Uint64 {
+    type Output = Self;
+
+    fn add(self, rhs: &'a Uint64) -> Self {
+        Uint64(self.u64().checked_add(rhs.u64()).unwrap())
+    }
+}
+
+impl ops::AddAssign<Uint64> for Uint64 {
+    fn add_assign(&mut self, rhs: Uint64) {
+        self.0 = self.0.checked_add(rhs.u64()).unwrap();
+    }
+}
+
+impl<'a> ops::AddAssign<&'a Uint64> for Uint64 {
+    fn add_assign(&mut self, rhs: &'a Uint64) {
+        self.0 = self.0.checked_add(rhs.u64()).unwrap();
+    }
+}
+
+impl Uint64 {
+    /// Returns `self * numerator / denominator`
+    pub fn multiply_ratio<A: Into<u64>, B: Into<u64>>(
+        &self,
+        numerator: A,
+        denominator: B,
+    ) -> Uint64 {
+        let numerator: u64 = numerator.into();
+        let denominator: u64 = denominator.into();
+        if denominator == 0 {
+            panic!("Denominator must not be zero");
+        }
+        // TODO: minimize rounding that takes place (using gcd algorithm)
+        let val = self.u64() * numerator / denominator;
+        Uint64::from(val)
+    }
+}
+
+impl Serialize for Uint64 {
+    /// 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 Uint64 {
+    /// Deserialized from an integer string using base 10
+    fn deserialize<D>(deserializer: D) -> Result<Uint64, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        deserializer.deserialize_str(Uint64Visitor)
+    }
+}
+
+struct Uint64Visitor;
+
+impl<'de> de::Visitor<'de> for Uint64Visitor {
+    type Value = Uint64;
+
+    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,
+    {
+        match v.parse::<u64>() {
+            Ok(u) => Ok(Uint64(u)),
+            Err(e) => Err(E::custom(format!("invalid Uint64 '{}' - {}", v, e))),
+        }
+    }
+}
+
+impl Sum<Uint64> for Uint64 {
+    fn sum<I: Iterator<Item = Uint64>>(iter: I) -> Self {
+        iter.fold(Uint64::zero(), ops::Add::add)
+    }
+}
+
+impl<'a> Sum<&'a Uint64> for Uint64 {
+    fn sum<I: Iterator<Item = &'a Uint64>>(iter: I) -> Self {
+        iter.fold(Uint64::zero(), ops::Add::add)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::{from_slice, to_vec};
+
+    #[test]
+    fn uint64_convert_into() {
+        let original = Uint64(12345);
+        let a = u64::from(original);
+        assert_eq!(a, 12345);
+
+        let original = Uint64(12345);
+        let a = String::from(original);
+        assert_eq!(a, "12345");
+    }
+
+    #[test]
+    fn uint64_convert_from() {
+        let a = Uint64::from(5u64);
+        assert_eq!(a.0, 5);
+
+        let a = Uint64::from(5u32);
+        assert_eq!(a.0, 5);
+
+        let a = Uint64::from(5u16);
+        assert_eq!(a.0, 5);
+
+        let a = Uint64::from(5u8);
+        assert_eq!(a.0, 5);
+
+        let result = Uint64::try_from("34567");
+        assert_eq!(result.unwrap().0, 34567);
+
+        let result = Uint64::try_from("1.23");
+        assert!(result.is_err());
+    }
+
+    #[test]
+    fn uint64_implements_display() {
+        let a = Uint64(12345);
+        assert_eq!(format!("Embedded: {}", a), "Embedded: 12345");
+        assert_eq!(a.to_string(), "12345");
+
+        let a = Uint64(0);
+        assert_eq!(format!("Embedded: {}", a), "Embedded: 0");
+        assert_eq!(a.to_string(), "0");
+    }
+
+    #[test]
+    fn uint64_is_zero_works() {
+        assert_eq!(Uint64::zero().is_zero(), true);
+        assert_eq!(Uint64(0).is_zero(), true);
+
+        assert_eq!(Uint64(1).is_zero(), false);
+        assert_eq!(Uint64(123).is_zero(), false);
+    }
+
+    #[test]
+    fn uint64_json() {
+        let orig = Uint64(1234567890987654321);
+        let serialized = to_vec(&orig).unwrap();
+        assert_eq!(serialized.as_slice(), b"\"1234567890987654321\"");
+        let parsed: Uint64 = from_slice(&serialized).unwrap();
+        assert_eq!(parsed, orig);
+    }
+
+    #[test]
+    fn uint64_compare() {
+        let a = Uint64(12345);
+        let b = Uint64(23456);
+
+        assert!(a < b);
+        assert!(b > a);
+        assert_eq!(a, Uint64(12345));
+    }
+
+    #[test]
+    #[allow(clippy::op_ref)]
+    fn uint64_math() {
+        let a = Uint64(12345);
+        let b = Uint64(23456);
+
+        // test + with owned and reference right hand side
+        assert_eq!(a + b, Uint64(35801));
+        assert_eq!(a + &b, Uint64(35801));
+
+        // test - with owned and reference right hand side
+        assert_eq!((b.checked_sub(a)).unwrap(), Uint64(11111));
+
+        // test += with owned and reference right hand side
+        let mut c = Uint64(300000);
+        c += b;
+        assert_eq!(c, Uint64(323456));
+        let mut d = Uint64(300000);
+        d += &b;
+        assert_eq!(d, Uint64(323456));
+
+        // error result on underflow (- would produce negative result)
+        let underflow_result = a.checked_sub(b);
+        let OverflowError {
+            operand1, operand2, ..
+        } = underflow_result.unwrap_err();
+        assert_eq!((operand1, operand2), (a.to_string(), b.to_string()));
+    }
+
+    #[test]
+    #[should_panic]
+    fn uint64_math_overflow_panics() {
+        // almost_max is 2^64 - 10
+        let almost_max = Uint64(18446744073709551606);
+        let _ = almost_max + Uint64(12);
+    }
+
+    #[test]
+    fn uint64_multiply_ratio_works() {
+        let base = Uint64(500);
+
+        // factor 1/1
+        assert_eq!(base.multiply_ratio(1u64, 1u64), Uint64(500));
+        assert_eq!(base.multiply_ratio(3u64, 3u64), Uint64(500));
+        assert_eq!(base.multiply_ratio(654321u64, 654321u64), Uint64(500));
+
+        // factor 3/2
+        assert_eq!(base.multiply_ratio(3u64, 2u64), Uint64(750));
+        assert_eq!(base.multiply_ratio(333333u64, 222222u64), Uint64(750));
+
+        // factor 2/3 (integer devision always floors the result)
+        assert_eq!(base.multiply_ratio(2u64, 3u64), Uint64(333));
+        assert_eq!(base.multiply_ratio(222222u64, 333333u64), Uint64(333));
+
+        // factor 5/6 (integer devision always floors the result)
+        assert_eq!(base.multiply_ratio(5u64, 6u64), Uint64(416));
+        assert_eq!(base.multiply_ratio(100u64, 120u64), Uint64(416));
+    }
+
+    #[test]
+    #[should_panic(expected = "Denominator must not be zero")]
+    fn uint64_multiply_ratio_panics_for_zero_denominator() {
+        Uint64(500).multiply_ratio(1u64, 0u64);
+    }
+
+    #[test]
+    fn sum_works() {
+        let nums = vec![Uint64(17), Uint64(123), Uint64(540), Uint64(82)];
+        let expected = Uint64(762);
+
+        let sum_as_ref = nums.iter().sum();
+        assert_eq!(expected, sum_as_ref);
+
+        let sum_as_owned = nums.into_iter().sum();
+        assert_eq!(expected, sum_as_owned);
+    }
+
+    #[test]
+    fn uint64_methods() {
+        // checked_*
+        assert!(matches!(
+            Uint64(u64::MAX).checked_add(Uint64(1)),
+            Err(OverflowError { .. })
+        ));
+        assert!(matches!(
+            Uint64(0).checked_sub(Uint64(1)),
+            Err(OverflowError { .. })
+        ));
+        assert!(matches!(
+            Uint64(u64::MAX).checked_mul(Uint64(2)),
+            Err(OverflowError { .. })
+        ));
+        assert!(matches!(
+            Uint64(u64::MAX).checked_div(Uint64(0)),
+            Err(DivideByZeroError { .. })
+        ));
+        assert!(matches!(
+            Uint64(u64::MAX).checked_div_euclid(Uint64(0)),
+            Err(DivideByZeroError { .. })
+        ));
+        assert!(matches!(
+            Uint64(u64::MAX).checked_rem(Uint64(0)),
+            Err(DivideByZeroError { .. })
+        ));
+
+        // saturating_*
+        assert_eq!(Uint64(u64::MAX).saturating_add(Uint64(1)), Uint64(u64::MAX));
+        assert_eq!(Uint64(0).saturating_sub(Uint64(1)), Uint64(0));
+        assert_eq!(Uint64(u64::MAX).saturating_mul(Uint64(2)), Uint64(u64::MAX));
+        assert_eq!(Uint64(u64::MAX).saturating_pow(2), Uint64(u64::MAX));
+
+        // wrapping_*
+        assert_eq!(Uint64(u64::MAX).wrapping_add(Uint64(1)), Uint64(0));
+        assert_eq!(Uint64(0).wrapping_sub(Uint64(1)), Uint64(u64::MAX));
+        assert_eq!(
+            Uint64(u64::MAX).wrapping_mul(Uint64(2)),
+            Uint64(u64::MAX - 1)
+        );
+        assert_eq!(Uint64(u64::MAX).wrapping_pow(2), Uint64(1));
+    }
+}
diff --git a/packages/std/src/timestamp.rs b/packages/std/src/timestamp.rs
index 2d0419163..c0e92eeea 100644
--- a/packages/std/src/timestamp.rs
+++ b/packages/std/src/timestamp.rs
@@ -1,18 +1,71 @@
+use schemars::JsonSchema;
+use serde::{Deserialize, Serialize};
+
+use crate::math::Uint64;
+
 /// A point in time in nanosecond precision.
 ///
-/// This type cannot represent any time before the UNIX epoch because both fields are unsigned.
-pub struct Timestamp {
-    /// Absolute time in seconds since the UNIX epoch (00:00:00 on 1970-01-01 UTC).
-    pub seconds: u64,
-    /// The fractional part time in nanoseconds since `time` (0 to 999999999).
-    pub nanos: u64,
-}
+/// This type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.
+#[derive(
+    Serialize, Deserialize, Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, JsonSchema,
+)]
+pub struct Timestamp(Uint64);
 
 impl Timestamp {
-    pub fn plus_seconds(&self, addition: u64) -> Timestamp {
-        Timestamp {
-            seconds: self.seconds + addition,
-            nanos: self.nanos,
-        }
+    /// Creates a timestamp from nanoseconds since epoch
+    pub const fn from_nanos(nanos_since_epoch: u64) -> Self {
+        Timestamp(Uint64::new(nanos_since_epoch))
+    }
+
+    /// Creates a timestamp from seconds since epoch
+    pub const fn from_seconds(seconds_since_epoch: u64) -> Self {
+        Timestamp(Uint64::new(seconds_since_epoch * 1_000_000_000))
+    }
+
+    pub const fn plus_seconds(&self, addition: u64) -> Timestamp {
+        let nanos = Uint64::new(self.0.u64() + addition * 1_000_000_000);
+        Timestamp(nanos)
+    }
+
+    pub const fn plus_nanos(&self, addition: u64) -> Timestamp {
+        let nanos = Uint64::new(self.0.u64() + addition);
+        Timestamp(nanos)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn timestamp_from_nanos() {
+        let t = Timestamp::from_nanos(123);
+        assert_eq!(t.0.u64(), 123);
+        let t = Timestamp::from_nanos(0);
+        assert_eq!(t.0.u64(), 0);
+    }
+
+    #[test]
+    fn timestamp_from_seconds() {
+        let t = Timestamp::from_seconds(123);
+        assert_eq!(t.0.u64(), 123_000_000_000);
+        let t = Timestamp::from_seconds(0);
+        assert_eq!(t.0.u64(), 0);
+    }
+
+    #[test]
+    fn timestamp_plus_seconds() {
+        let sum = Timestamp::from_nanos(123).plus_seconds(42);
+        assert_eq!(sum.0.u64(), 42_000_000_123);
+        let sum = Timestamp::from_nanos(123).plus_seconds(0);
+        assert_eq!(sum.0.u64(), 123);
+    }
+
+    #[test]
+    fn timestamp_plus_nanos() {
+        let sum = Timestamp::from_nanos(123).plus_nanos(3);
+        assert_eq!(sum.0.u64(), 126);
+        let sum = Timestamp::from_nanos(123).plus_nanos(0);
+        assert_eq!(sum.0.u64(), 123);
     }
 }
diff --git a/packages/std/src/types.rs b/packages/std/src/types.rs
index fc6ceaa65..d548a46da 100644
--- a/packages/std/src/types.rs
+++ b/packages/std/src/types.rs
@@ -67,10 +67,8 @@ pub struct BlockInfo {
 impl BlockInfo {
     /// Returns the block creation time as a Timestamp in nanosecond precision
     pub fn timestamp(&self) -> Timestamp {
-        Timestamp {
-            seconds: self.time,
-            nanos: self.time_nanos,
-        }
+        let nanos_since_epoch = self.time * 1_000_000_000 + self.time_nanos;
+        Timestamp::from_nanos(nanos_since_epoch)
     }
 }
 


Test patch:
diff --git a/contracts/ibc-reflect-send/tests/integration.rs b/contracts/ibc-reflect-send/tests/integration.rs
index cf512a126..a6d3fbd6a 100644
--- a/contracts/ibc-reflect-send/tests/integration.rs
+++ b/contracts/ibc-reflect-send/tests/integration.rs
@@ -239,7 +239,7 @@ fn send_remote_funds() {
             assert_eq!(transfer_channel_id, channel_id.as_str());
             assert_eq!(remote_addr, to_address.as_str());
             assert_eq!(&coin(12344, "utrgd"), amount);
-            assert!(matches!(timeout, IbcTimeout::TimestampNanos { .. }));
+            assert!(matches!(timeout, IbcTimeout::Timestamp { .. }));
         }
         o => panic!("unexpected message: {:?}", o),
     }