Repository: paritytech/ink
Pull request #104 (2019-05-29 17:08:28)

ink! ABI
Prototype implementation of ink! meta data.
This will eventually replace the current system of API description generation.

`ink_abi` will be the library behind the two-phase ABI file generation and provides the data structures and serialization routines that are going to be constructed by `ink_lang` during compilation of a smart contract.

It can handle type aliases, differentiate types with the same name.

Future plans are to also integrate storage layout description as well as custom type layout description into it.

**TODO**

- [x] Implement `type-metadata` crate for handling runtime type information.
    - Repository: https://github.com/type-metadata/type-metadata
- [x] Implement `ink_abi` sub-crate providing traits and mechanics for ABI generation.
- [x] Integrate `ink_abi` trait implementations into `ink_core`.
- [x] Adjust `ink_cli` template for ink! smart contract generation.
- [x] Implement ABI generation into `ink_lang`
- [ ] ~~Implement 2-phase ABI generation procedure in `cargo-contract`~~ (not needed with chosen design)
- [x] Remove deprecated ABI generation (via `generate-api-description` crate feature)
- [x] Implement derive macros for `HasLayout`
- [x] Adjust the rest of the examples to new `ink_abi` project structure
- [x] Fix remaining failing `ink_lang` tests
- [ ] Write docs about how to generate ABI files using `ink_abi`
- [x] Open questions:
    - [x] Shall we rename `.tool` directory to `.ink` ? (same as `.git`)
- [ ] Minor issues:
    - [x] Trim generated doc strings so that they no longer include `= \" ` and `\"` as start and end respectively.
    - [ ] Mirror storage layout of `ExecEnv<State>` instead of directly taking `State`
    - [x] Fix bug that `test-env` crate feature has to be enabled for `ink-generate-abi` 

Code currently generated for Flipper example contract:

```rust
#[cfg(feature = "generate-abi")]
fn ink_generate_abi() -> ink_abi::InkProject {
    let contract = {
        ink_abi::ContractSpec::new("Flipper")
			.on_deploy(
				ink_abi::DeploySpec::new()
					.args(<[_]>::into_vec(box []))
					.docs(<[_]>::into_vec(box [
						"= \" Initializes our state to `false` upon deploying our smart contract.\""
					]))
					.done()
				)
			.messages(<[_]>::into_vec(box [
				ink_abi::MessageSpec::new("flip")
					.selector(970692492u32)
					.mutates(true)
					.args(<[_]>::into_vec(box []))
					.docs(<[_]>::into_vec(box [
						"= \" Flips the current state of our smart contract.\""
					]))
					.returns(ink_abi::ReturnTypeSpec::none())
					.done(),
                ink_abi::MessageSpec::new("get")
					.selector(4266279973u32)
					.mutates(false)
					.args(<[_]>::into_vec(box []))
					.docs(<[_]>::into_vec(box [
						"= \" Returns the current state.\""
					]))
					.returns(ink_abi::ReturnTypeSpec::new::<bool>())
					.done()
				])
			)
			.events(<[_]>::into_vec(box []))
			.docs(<[_]>::into_vec(box []))
			.done()
    };
    let layout = {
        impl ink_abi::HasLayout for Flipper {
            fn layout(&self) -> ink_abi::StorageLayout {
                {
                    ::std::rt::begin_panic(
                        "not yet implemented",
                        &("src/lib.rs", 26u32, 1u32),
                    )
                }
            }
        }
        unsafe {
            use ink_abi::HasLayout as _;
            use ink_core::storage::alloc::AllocateUsing as _;
            Flipper::allocate_using(
                &mut ink_core::storage::alloc::BumpAlloc::from_raw_parts(
                    ink_core::storage::Key([0x0; 32]),
                ),
            )
            .layout()
        }
    };
    ink_abi::InkProject::new(layout, contract)
}
```

Metadata created for the Flipper example contract:

```json
{
  "registry": {
    "strings": [
      "Flipper",
      "flipper",
      "value",
      "Value",
      "ink_core",
      "storage",
      "cell",
      "SyncCell",
      "sync_cell",
      "Key",
      "key",
      "flip",
      "get"
    ],
    "types": [
      {
        "id": "bool",
        "def": "builtin"
      },
      {
        "id": "u8",
        "def": "builtin"
      },
      {
        "id": {
          "array.len": 32,
          "array.type": 7
        },
        "def": "builtin"
      },
      {
        "id": {
          "custom.name": 10,
          "custom.namespace": [
            5,
            6,
            11
          ],
          "custom.params": []
        },
        "def": {
          "tuple_struct.types": [
            6
          ]
        }
      },
      {
        "id": {
          "custom.name": 8,
          "custom.namespace": [
            5,
            6,
            7,
            9
          ],
          "custom.params": [
            3
          ]
        },
        "def": {
          "struct.fields": [
            {
              "name": 7,
              "type": 5
            }
          ]
        }
      },
      {
        "id": {
          "custom.name": 4,
          "custom.namespace": [
            5,
            6,
            3
          ],
          "custom.params": [
            3
          ]
        },
        "def": {
          "struct.fields": [
            {
              "name": 7,
              "type": 4
            }
          ]
        }
      },
      {
        "id": {
          "custom.name": 1,
          "custom.namespace": [
            2
          ],
          "custom.params": []
        },
        "def": {
          "struct.fields": [
            {
              "name": 3,
              "type": 2
            }
          ]
        }
      }
    ]
  },
  "storage": {
    "struct.type": 1,
    "struct.fields": [
      {
        "name": 3,
        "layout": {
          "struct.type": 2,
          "struct.fields": [
            {
              "name": 7,
              "layout": {
                "range.offset": [
                  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
                ],
                "range.len": 1,
                "range.elem_type": 3
              }
            }
          ]
        }
      }
    ]
  },
  "contract": {
    "name": 1,
    "deploy": {
      "args": [],
      "docs": [
        "Initializes our state to `false` upon deploying our smart contract."
      ]
    },
    "messages": [
      {
        "name": 12,
        "selector": 970692492,
        "mutates": true,
        "args": [],
        "return_type": null,
        "docs": [
          "Flips the current state of our smart contract."
        ]
      },
      {
        "name": 13,
        "selector": 4266279973,
        "mutates": false,
        "args": [],
        "return_type": 3,
        "docs": [
          "Returns the current state."
        ]
      }
    ],
    "events": [],
    "docs": []
  }
}
```

codecov-io: # [Codecov](https://codecov.io/gh/paritytech/ink/pull/104?src=pr&el=h1) Report
> Merging [#104](https://codecov.io/gh/paritytech/ink/pull/104?src=pr&el=desc) into [master](https://codecov.io/gh/paritytech/ink/commit/b6ab9b3c3f956a052d2a3059046403d1f6dcabd6?src=pr&el=desc) will **increase** coverage by `0.16%`.
> The diff coverage is `89.41%`.

[![Impacted file tree graph](https://codecov.io/gh/paritytech/ink/pull/104/graphs/tree.svg?width=650&token=tYyVaCZb7o&height=150&src=pr)](https://codecov.io/gh/paritytech/ink/pull/104?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #104      +/-   ##
==========================================
+ Coverage    84.3%   84.46%   +0.16%     
==========================================
  Files          60       64       +4     
  Lines        5205     5375     +170     
==========================================
+ Hits         4388     4540     +152     
- Misses        817      835      +18
```


| [Impacted Files](https://codecov.io/gh/paritytech/ink/pull/104?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [abi/src/lib.rs](https://codecov.io/gh/paritytech/ink/pull/104/diff?src=pr&el=tree#diff-YWJpL3NyYy9saWIucnM=) | `0% <0%> (ø)` | |
| [abi/src/specs.rs](https://codecov.io/gh/paritytech/ink/pull/104/diff?src=pr&el=tree#diff-YWJpL3NyYy9zcGVjcy5ycw==) | `8.33% <8.33%> (ø)` | |
| [abi/src/tuple\_vec.rs](https://codecov.io/gh/paritytech/ink/pull/104/diff?src=pr&el=tree#diff-YWJpL3NyYy90dXBsZV92ZWMucnM=) | `94.91% <94.91%> (ø)` | |
| [abi/src/abi\_type.rs](https://codecov.io/gh/paritytech/ink/pull/104/diff?src=pr&el=tree#diff-YWJpL3NyYy9hYmlfdHlwZS5ycw==) | `96.93% <96.93%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/paritytech/ink/pull/104?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/paritytech/ink/pull/104?src=pr&el=footer). Last update [b6ab9b3...ec5caf6](https://codecov.io/gh/paritytech/ink/pull/104?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

Robbepop: > 
> 
> LGTM! 🌮

🍰

Patch:
diff --git a/.travis.yml b/.travis.yml
index 68b52fa0f3..701c33eb4d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -7,7 +7,7 @@ rust:
   # - stable
   # - beta
   - nightly
-  - nightly-2019-07-19
+  - nightly-2019-08-09
 
 matrix:
   allow_failures:
@@ -35,6 +35,7 @@ install:
   # Print rustc and cargo versions
   - rustc -vV
   - cargo -vV
+  - bash --version
   # Install cargo-kcov Cargo plugin
   - cargo install --force cargo-kcov
   - cargo kcov -vV
@@ -61,92 +62,19 @@ before_script:
 
 script:
 - |
-  cargo check --verbose --all --all-features
-  check_all_features=$?
+  ./scripts/check-workspace.sh
+  check_workspace=$?
 
-  cargo check --verbose --all --no-default-features
-  check_no_default=$?
+  ./scripts/check-examples.sh
+  check_examples=$?
 
-  cargo fmt --verbose --all -- --check
-  fmt=$?
-
-  cargo clippy --verbose --all --all-features -- -D warnings
-  clippy_all_features=$?
-
-  cargo clippy --verbose --all --no-default-features -- -D warnings
-  clippy_no_default=$?
-
-  cargo test --verbose --all --all-features
-  test=$?
-
-  cargo kcov --verbose --coveralls --all --no-clean-rebuild
-  kcov=$?
-  bash <(curl -s https://codecov.io/bash)
-
-  cargo build --verbose --all --no-default-features --release --target=wasm32-unknown-unknown
-  build_wasm=$?
-
-  echo "CI Summary"
-  echo "=========="
-  echo ""
-  if [ $check_all_features -eq 0 ]
+  if [ $check_examples -eq 0 ] && [ $check_workspace -eq 0 ]
   then
-    echo "compile (all-features):        ok"
-  else
-    echo "compile (all-features):        ERROR"
-  fi
+    # Execute and upload kcov results
+    cargo kcov --verbose --coveralls --all --no-clean-rebuild
+    kcov=$?
+    bash <(curl -s https://codecov.io/bash)
 
-  if [ $check_no_default -eq 0 ]
-  then
-    echo "compile (no-default-features): ok"
-  else
-    echo "compile (no-default-features): ERROR"
-  fi
-
-  if [ $fmt -eq 0 ]
-  then
-    echo "formatting:                    ok"
-  else
-    echo "formatting:                    ERROR"
-  fi
-
-  if [ $clippy_all_features -eq 0 ]
-  then
-    echo "clippy (all-features):         ok"
-  else
-    echo "clippy (all-features):         ERROR"
-  fi
-
-  if [ $clippy_no_default -eq 0 ]
-  then
-    echo "clippy (no-default-features):  ok"
-  else
-    echo "clippy (no-default-features):  ERROR"
-  fi
-
-  if [ $test -eq 0 ]
-  then
-    echo "test (all-features):           ok"
-  else
-    echo "test (all-features):           ERROR"
-  fi
-
-  if [ $build_wasm -eq 0 ]
-  then
-    echo "build Wasm:                    ok"
-  else
-    echo "build Wasm:                    ERROR"
-  fi
-
-  if [ $kcov -eq 0 ]
-  then
-    echo "coverage analysis:             ok"
-  else
-    echo "coverage analysis:             ERROR"
-  fi
-
-  if [ $check_all_features -eq 0 ] && [ $check_no_default -eq 0 ] && [ $fmt -eq 0 ] && [ $clippy_all_features -eq 0 ] && [ $clippy_no_default -eq 0 ] && [ $test -eq 0 ] && [ $build_wasm -eq 0 ]
-  then
     echo "All checks have passed!"
     exit 0
   else
diff --git a/Cargo.toml b/Cargo.toml
index 410f19745f..c881e66362 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,9 +6,11 @@ members = [
     "model",
     "lang",
     "cli",
+    "abi",
 ]
 exclude = [
     "examples/",
+    "cli/template/",
 ]
 
 [profile.release]
diff --git a/README.md b/README.md
index 6ca0a80f32..d533daac96 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,8 @@
 # ink! - Parity's ink to write smart contracts
 
-| Linux              | Codecov              | Coveralls              | LoC              | Docs (Core)       | Docs (Model)      |
-|:------------------:|:--------------------:|:----------------------:|:----------------:|:-----------------:|:-----------------:|
-| [![linux][A1]][A2] | [![codecov][C1]][C2] | [![coveralls][D1]][D2] | [![loc][E1]][E2] | [![docs][F1]][F2] | [![docs][G1]][G2] |
+| Linux              | Codecov              | Coveralls              | LoC              |
+|:------------------:|:--------------------:|:----------------------:|:----------------:|
+| [![linux][A1]][A2] | [![codecov][C1]][C2] | [![coveralls][D1]][D2] | [![loc][E1]][E2] |
 
 [A1]: https://travis-ci.org/paritytech/ink.svg?branch=master
 [A2]: https://travis-ci.org/paritytech/ink
@@ -16,19 +16,28 @@
 [E1]: https://tokei.rs/b1/github/paritytech/ink?category=code
 [E2]: https://github.com/Aaronepower/tokei#badges
 
-[F1]: https://img.shields.io/badge/docs-nightly-black.svg
-[F2]: https://paritytech.github.io/ink/ink_core/index.html
+[F1]: https://img.shields.io/badge/docs-core-blue.svg
+[F2]: https://paritytech.github.io/ink/ink_core
 
-[G1]: https://img.shields.io/badge/docs-nightly-purple.svg
-[G2]: https://paritytech.github.io/ink/ink_model/index.html
+[G1]: https://img.shields.io/badge/docs-model-blue.svg
+[G2]: https://paritytech.github.io/ink/ink_model
+
+[H1]: https://img.shields.io/badge/docs-abi-blue.svg
+[H2]: https://paritytech.github.io/ink/ink_abi
 
 **IMPORTANT NOTE:** WORK IN PROGRESS! Do not expect this to be working.
 
-ink is an [eDSL](https://wiki.haskell.org/Embedded_domain_specific_language) to write WebAssembly based smart contracts using the Rust programming language.
+ink! is an [eDSL](https://wiki.haskell.org/Embedded_domain_specific_language) to write WebAssembly based smart contracts using the Rust programming language targeting Substrate blockchains.
+
+## Developer Documentation
+
+| `ink_abi` | `ink_core` | `ink_model` |
+|-----------|------------|-------------|
+| [![][H1]][H2] | [![][F1]][F2] | [![][G1]][G2] |
 
 ## Example
 
-Below is an example using the eDSL demonstrating a simple Flipper smart contract
+Below is an example using ink! demonstrating a simple Flipper smart contract
 that has a boolean state that can be flipped or returned.
 
 ```rust
diff --git a/abi/Cargo.toml b/abi/Cargo.toml
new file mode 100644
index 0000000000..315be70819
--- /dev/null
+++ b/abi/Cargo.toml
@@ -0,0 +1,34 @@
+[package]
+name = "ink_abi"
+version = "0.1.0"
+authors = ["Parity Technologies <admin@parity.io>"]
+edition = "2018"
+
+license = "GPL-3.0"
+readme = "README.md"
+
+description = "Rust based eDSL for writing smart contracts for Substrate"
+keywords = ["wasm", "parity", "webassembly", "blockchain", "edsl"]
+categories = ["no-std", "embedded"]
+
+include = ["/Cargo.toml", "src/**/*.rs", "/README.md", "/LICENSE"]
+
+[dependencies]
+serde = { version = "1.0", default-features = false, features = ["derive", "alloc"] }
+derive_more = { version = "0.14", features = ["no_std"] }
+ink_abi_derive = { version = "0.1.0", path = "derive", default-features = false, optional = true }
+type-metadata = { git = "https://github.com/type-metadata/type-metadata.git", default-features = false, features = ["derive"] }
+
+[features]
+default = [
+    "std",
+    "derive",
+]
+derive = [
+    "ink_abi_derive"
+]
+std = [
+    "ink_abi_derive/std",
+    "serde/std",
+    "type-metadata/std",
+]
diff --git a/abi/LICENSE b/abi/LICENSE
new file mode 120000
index 0000000000..ea5b60640b
--- /dev/null
+++ b/abi/LICENSE
@@ -0,0 +1,1 @@
+../LICENSE
\ No newline at end of file
diff --git a/abi/README.md b/abi/README.md
new file mode 120000
index 0000000000..32d46ee883
--- /dev/null
+++ b/abi/README.md
@@ -0,0 +1,1 @@
+../README.md
\ No newline at end of file
diff --git a/abi/derive/Cargo.toml b/abi/derive/Cargo.toml
new file mode 100644
index 0000000000..5617a60fb3
--- /dev/null
+++ b/abi/derive/Cargo.toml
@@ -0,0 +1,20 @@
+[package]
+name = "ink_abi_derive"
+version = "0.1.0"
+authors = ["Parity Technologies <admin@parity.io>"]
+edition = "2018"
+
+license = "Apache-2.0"
+readme = "../README.md"
+
+[lib]
+proc-macro = true
+
+[dependencies]
+quote = "0.6"
+syn = { version = "0.15", features = ["full"] }
+proc-macro2 = "0.4"
+
+[features]
+default = ["std"]
+std = []
diff --git a/abi/derive/src/error.rs b/abi/derive/src/error.rs
new file mode 100644
index 0000000000..6de18ea7c0
--- /dev/null
+++ b/abi/derive/src/error.rs
@@ -0,0 +1,31 @@
+// Copyright 2018-2019 Parity Technologies (UK) Ltd.
+// This file is part of ink!.
+//
+// ink! is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// ink! is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with ink!.  If not, see <http://www.gnu.org/licenses/>.
+
+macro_rules! bail {
+    ($($args:tt)*) => {
+        return Err(format_err!($($args)*).into())
+    }
+}
+
+macro_rules! format_err {
+    ($tokens:expr, $($msg:tt)*) => {
+        match &$tokens {
+            t => {
+                syn::parse::Error::new_spanned(t, format_args!($($msg)*))
+            }
+        }
+    }
+}
diff --git a/abi/derive/src/has_layout.rs b/abi/derive/src/has_layout.rs
new file mode 100644
index 0000000000..72e9c204fb
--- /dev/null
+++ b/abi/derive/src/has_layout.rs
@@ -0,0 +1,105 @@
+// Copyright 2018-2019 Parity Technologies (UK) Ltd.
+// This file is part of ink!.
+//
+// ink! is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// ink! is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with ink!.  If not, see <http://www.gnu.org/licenses/>.
+
+use crate::impl_wrapper::wrap;
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+use syn::{
+    self,
+    parse::Result,
+    parse_quote,
+    punctuated::Punctuated,
+    Data,
+    DataStruct,
+    DeriveInput,
+    Field,
+    Fields,
+    Token,
+};
+
+pub fn generate(input: TokenStream2) -> TokenStream2 {
+    match generate_impl(input.into()) {
+        Ok(output) => output.into(),
+        Err(err) => err.to_compile_error().into(),
+    }
+}
+
+pub fn generate_impl(input: TokenStream2) -> Result<TokenStream2> {
+    let mut ast: DeriveInput = syn::parse2(input)?;
+
+    ast.generics.type_params_mut().for_each(|p| {
+        p.bounds.push(parse_quote!(_ink_abi::HasLayout));
+    });
+
+    let ident = &ast.ident;
+    let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
+
+    let layout = match &ast.data {
+        Data::Struct(ref s) => generate_struct_layout(s),
+        Data::Enum(ref _e) => bail!(&ast, "enums are not supported"),
+        Data::Union(ref _u) => bail!(&ast, "unions are not supported"),
+    };
+
+    let has_layout_impl = quote! {
+        impl #impl_generics _ink_abi::HasLayout for #ident #ty_generics #where_clause {
+            fn layout(&self) -> _ink_abi::StorageLayout {
+                #layout.into()
+            }
+        }
+    };
+
+    Ok(wrap(ident, "HAS_LAYOUT", has_layout_impl).into())
+}
+
+fn generate_fields_layout<'a>(
+    fields: &'a Punctuated<Field, Token![,]>,
+) -> impl Iterator<Item = TokenStream2> + 'a {
+    fields.iter().enumerate().map(|(n, field)| {
+        let ident = &field.ident;
+        if let Some(ident) = ident {
+            quote! {
+                _ink_abi::LayoutField::new(stringify!(#ident), self.#ident.layout())
+            }
+        } else {
+            quote! {
+                _ink_abi::LayoutField::new(stringify!(#n), self.#n.layout())
+            }
+        }
+    })
+}
+
+fn generate_struct_fields_layout(fields: &Punctuated<Field, Token![,]>) -> TokenStream2 {
+    let fields_layout = generate_fields_layout(fields);
+    quote! {
+        use type_metadata::Metadata as _;
+        _ink_abi::LayoutStruct::new(Self::meta_type(), __core::vec![
+            #( #fields_layout, )*
+        ])
+    }
+}
+
+fn generate_struct_layout(data_struct: &DataStruct) -> TokenStream2 {
+    match data_struct.fields {
+        Fields::Named(ref fs) => generate_struct_fields_layout(&fs.named),
+        Fields::Unnamed(ref fs) => generate_struct_fields_layout(&fs.unnamed),
+        Fields::Unit => {
+            quote! {
+                use type_metadata::Metadata as _;
+                _ink_abi::LayoutStruct::new(Self::meta_type(), __core::vec![])
+            }
+        }
+    }
+}
diff --git a/abi/derive/src/impl_wrapper.rs b/abi/derive/src/impl_wrapper.rs
new file mode 100644
index 0000000000..8effa39a45
--- /dev/null
+++ b/abi/derive/src/impl_wrapper.rs
@@ -0,0 +1,69 @@
+// Copyright 2018-2019 Parity Technologies (UK) Ltd.
+// This file is part of ink!.
+//
+// ink! is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// ink! is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with ink!.  If not, see <http://www.gnu.org/licenses/>.
+
+#[cfg(not(feature = "std"))]
+use alloc::{
+    format,
+    string::{
+        String,
+        ToString,
+    },
+};
+
+use proc_macro2::{
+    Span,
+    TokenStream as TokenStream2,
+};
+use quote::quote;
+use syn::Ident;
+
+pub fn wrap(
+    ident: &Ident,
+    trait_name: &'static str,
+    impl_quote: TokenStream2,
+) -> TokenStream2 {
+    let mut renamed = String::from(format!("_IMPL_{}_FOR_", trait_name));
+    renamed.push_str(ident.to_string().trim_start_matches("r#"));
+    let dummy_const = Ident::new(&renamed, Span::call_site());
+
+    quote! {
+        #[allow(non_upper_case_globals, unused_attributes, unused_qualifications)]
+        const #dummy_const: () = {
+            #[allow(unknown_lints)]
+            #[cfg_attr(feature = "cargo-clippy", allow(useless_attribute))]
+            #[allow(rust_2018_idioms)]
+            use type_metadata as _type_metadata;
+            use ink_abi as _ink_abi;
+
+            #[cfg(not(feature = "std"))]
+            extern crate alloc;
+
+            #[cfg(feature = "std")]
+            mod __core {
+                pub use ::core::*;
+                pub use ::std::{vec, vec::Vec};
+            }
+
+            #[cfg(not(feature = "std"))]
+            mod __core {
+                pub use ::core::*;
+                pub use ::alloc::{vec, vec::Vec};
+            }
+
+            #impl_quote;
+        };
+    }
+}
diff --git a/abi/derive/src/lib.rs b/abi/derive/src/lib.rs
new file mode 100644
index 0000000000..50134b32d3
--- /dev/null
+++ b/abi/derive/src/lib.rs
@@ -0,0 +1,33 @@
+// Copyright 2018-2019 Parity Technologies (UK) Ltd.
+// This file is part of ink!.
+//
+// ink! is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// ink! is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with ink!.  If not, see <http://www.gnu.org/licenses/>.
+
+#![cfg_attr(not(feature = "std"), no_std)]
+
+extern crate alloc;
+extern crate proc_macro;
+
+#[macro_use]
+mod error;
+
+mod has_layout;
+mod impl_wrapper;
+
+use proc_macro::TokenStream;
+
+#[proc_macro_derive(HasLayout)]
+pub fn has_layout(input: TokenStream) -> TokenStream {
+    has_layout::generate(input.into()).into()
+}
diff --git a/abi/src/layout.rs b/abi/src/layout.rs
new file mode 100644
index 0000000000..038ddf122a
--- /dev/null
+++ b/abi/src/layout.rs
@@ -0,0 +1,207 @@
+// Copyright 2018-2019 Parity Technologies (UK) Ltd.
+// This file is part of ink!.
+//
+// ink! is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// ink! is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with ink!.  If not, see <http://www.gnu.org/licenses/>.
+
+use derive_more::From;
+use serde::Serialize;
+use type_metadata::{
+    form::{
+        CompactForm,
+        Form,
+        MetaForm,
+    },
+    IntoCompact,
+    Registry,
+};
+
+#[cfg(not(feature = "std"))]
+use alloc::vec::Vec;
+
+/// Implemented by types that have a storage layout.
+///
+/// Has to be used on previously allocated instances of the types.
+pub trait HasLayout {
+    fn layout(&self) -> StorageLayout;
+}
+
+/// Either a concrete layout bound or another layout sub-struct.
+#[derive(Debug, PartialEq, Eq, Serialize, From)]
+#[serde(bound = "F::TypeId: Serialize")]
+#[serde(untagged)]
+pub enum StorageLayout<F: Form = MetaForm> {
+    /// A concrete layout bound.
+    Range(LayoutRange<F>),
+    /// A nested sub-struct with layout bounds.
+    Struct(LayoutStruct<F>),
+}
+
+impl IntoCompact for StorageLayout {
+    type Output = StorageLayout<CompactForm>;
+
+    fn into_compact(self, registry: &mut Registry) -> Self::Output {
+        match self {
+            StorageLayout::Range(layout_range) => {
+                StorageLayout::Range(layout_range.into_compact(registry))
+            }
+            StorageLayout::Struct(layout_struct) => {
+                StorageLayout::Struct(layout_struct.into_compact(registry))
+            }
+        }
+    }
+}
+
+/// A concrete range of keys.
+///
+/// Basically a thin-wrapper around keys from `ink_core` library for serialization purposes.
+#[derive(Debug, PartialEq, Eq, From, Serialize)]
+#[serde(transparent)]
+pub struct LayoutKey(
+    /// Internals must be compatible with `ink_core::storage::Key`.
+    pub [u8; 32],
+);
+
+/// A struct storage layout.
+#[derive(Debug, PartialEq, Eq, Serialize)]
+#[serde(bound = "F::TypeId: Serialize")]
+pub struct LayoutStruct<F: Form = MetaForm> {
+    #[serde(rename = "struct.type")]
+    self_ty: F::TypeId,
+    /// The sub-fields of the struct.
+    #[serde(rename = "struct.fields")]
+    fields: Vec<LayoutField<F>>,
+}
+
+impl LayoutStruct {
+    /// Creates a new layout struct.
+    pub fn new<F>(self_ty: <MetaForm as Form>::TypeId, fields: F) -> Self
+    where
+        F: IntoIterator<Item = LayoutField>,
+    {
+        Self {
+            self_ty,
+            fields: fields.into_iter().collect::<Vec<_>>(),
+        }
+    }
+}
+
+impl IntoCompact for LayoutStruct {
+    type Output = LayoutStruct<CompactForm>;
+
+    fn into_compact(self, registry: &mut Registry) -> Self::Output {
+        LayoutStruct {
+            self_ty: registry.register_type(&self.self_ty),
+            fields: self
+                .fields
+                .into_iter()
+                .map(|field| field.into_compact(registry))
+                .collect::<Vec<_>>(),
+        }
+    }
+}
+
+/// The layout for a particular field of a struct layout.
+#[derive(Debug, PartialEq, Eq, Serialize)]
+#[serde(bound = "F::TypeId: Serialize")]
+pub struct LayoutField<F: Form = MetaForm> {
+    /// The name of the field.
+    name: F::String,
+    /// The kind of the field.
+    ///
+    /// This is either a direct layout bound
+    /// or another recursive layout sub-struct.
+    #[serde(rename = "layout")]
+    sub_layout: StorageLayout<F>,
+}
+
+impl LayoutField {
+    /// Creates a new layout field from the given name and sub-structural layout.
+    pub fn new(name: <MetaForm as Form>::String, sub_layout: StorageLayout) -> Self {
+        Self { name, sub_layout }
+    }
+
+    /// Creates a new layout field for the given field instance.
+    pub fn of<T>(name: <MetaForm as Form>::String, field: &T) -> Self
+    where
+        T: HasLayout,
+    {
+        Self::new(name, field.layout())
+    }
+}
+
+impl IntoCompact for LayoutField {
+    type Output = LayoutField<CompactForm>;
+
+    fn into_compact(self, registry: &mut Registry) -> Self::Output {
+        LayoutField {
+            name: registry.register_string(self.name),
+            sub_layout: self.sub_layout.into_compact(registry),
+        }
+    }
+}
+
+/// Direct range of associated storage keys.
+///
+/// This may represent either a single cell, a whole chunk of cells or something in between.
+#[derive(Debug, PartialEq, Eq, Serialize)]
+#[serde(bound = "F::TypeId: Serialize")]
+pub struct LayoutRange<F: Form = MetaForm> {
+    /// The single key for cells or the starting key address for chunks.
+    #[serde(rename = "range.offset")]
+    offset: LayoutKey,
+    /// The amount of associated key addresses starting from the offset key.
+    #[serde(rename = "range.len")]
+    len: u32,
+    /// The element type stored under the associated keys.
+    #[serde(rename = "range.elem_type")]
+    elem_ty: F::TypeId,
+}
+
+impl IntoCompact for LayoutRange {
+    type Output = LayoutRange<CompactForm>;
+
+    fn into_compact(self, registry: &mut Registry) -> Self::Output {
+        LayoutRange {
+            offset: self.offset,
+            len: self.len,
+            elem_ty: registry.register_type(&self.elem_ty),
+        }
+    }
+}
+
+impl LayoutRange {
+    /// Creates a layout range representing a single cell.
+    pub fn cell<K>(at: K, elem_ty: <MetaForm as Form>::TypeId) -> Self
+    where
+        K: Into<LayoutKey>,
+    {
+        Self {
+            offset: at.into(),
+            len: 1,
+            elem_ty,
+        }
+    }
+
+    /// Creates a layout range for a whole chunk starting at the offset key.
+    pub fn chunk<K>(offset: K, elem_ty: <MetaForm as Form>::TypeId) -> Self
+    where
+        K: Into<LayoutKey>,
+    {
+        Self {
+            offset: offset.into(),
+            len: 0xFFFF_FFFF,
+            elem_ty,
+        }
+    }
+}
diff --git a/abi/src/lib.rs b/abi/src/lib.rs
new file mode 100644
index 0000000000..fa4f7304e2
--- /dev/null
+++ b/abi/src/lib.rs
@@ -0,0 +1,77 @@
+// Copyright 2018-2019 Parity Technologies (UK) Ltd.
+// This file is part of ink!.
+//
+// ink! is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// ink! is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with ink!.  If not, see <http://www.gnu.org/licenses/>.
+
+#![cfg_attr(not(feature = "std"), no_std)]
+
+#[cfg(not(feature = "std"))]
+extern crate alloc;
+
+mod layout;
+mod specs;
+
+#[cfg(feature = "derive")]
+pub use ink_abi_derive::HasLayout;
+
+pub use self::{
+    layout::{
+        HasLayout,
+        LayoutField,
+        LayoutKey,
+        LayoutRange,
+        LayoutStruct,
+        StorageLayout,
+    },
+    specs::{
+        ContractSpec,
+        DeploySpec,
+        EventParamSpec,
+        EventSpec,
+        MessageParamSpec,
+        MessageSpec,
+        ReturnTypeSpec,
+    },
+};
+
+use serde::Serialize;
+use type_metadata::{
+    form::CompactForm,
+    IntoCompact as _,
+    Registry,
+};
+
+#[derive(Debug, Serialize)]
+pub struct InkProject {
+    registry: Registry,
+    #[serde(rename = "storage")]
+    layout: StorageLayout<CompactForm>,
+    #[serde(rename = "contract")]
+    spec: ContractSpec<CompactForm>,
+}
+
+impl InkProject {
+    pub fn new<L, S>(layout: L, spec: S) -> Self
+    where
+        L: Into<StorageLayout>,
+        S: Into<ContractSpec>,
+    {
+        let mut registry = Registry::new();
+        Self {
+            layout: layout.into().into_compact(&mut registry),
+            spec: spec.into().into_compact(&mut registry),
+            registry,
+        }
+    }
+}
diff --git a/abi/src/specs.rs b/abi/src/specs.rs
new file mode 100644
index 0000000000..7c2ad3a322
--- /dev/null
+++ b/abi/src/specs.rs
@@ -0,0 +1,614 @@
+// Copyright 2018-2019 Parity Technologies (UK) Ltd.
+// This file is part of ink!.
+//
+// ink! is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// ink! is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with ink!.  If not, see <http://www.gnu.org/licenses/>.
+
+use core::marker::PhantomData;
+use serde::Serialize;
+use type_metadata::{
+    form::{
+        CompactForm,
+        Form,
+        MetaForm,
+    },
+    IntoCompact,
+    Metadata,
+    Registry,
+};
+
+#[cfg(not(feature = "std"))]
+use alloc::{
+    vec,
+    vec::Vec,
+};
+
+/// Describes a contract.
+#[derive(Debug, PartialEq, Eq, Serialize)]
+#[serde(bound = "F::TypeId: Serialize")]
+pub struct ContractSpec<F: Form = MetaForm> {
+    /// The name of the contract.
+    name: F::String,
+    /// The deploy handler of the contract.
+    deploy: DeploySpec<F>,
+    /// The external messages of the contract.
+    messages: Vec<MessageSpec<F>>,
+    /// The events of the contract.
+    events: Vec<EventSpec<F>>,
+    /// The contract documentation.
+    docs: Vec<&'static str>,
+}
+
+impl IntoCompact for ContractSpec {
+    type Output = ContractSpec<CompactForm>;
+
+    fn into_compact(self, registry: &mut Registry) -> Self::Output {
+        ContractSpec {
+            name: registry.register_string(&self.name),
+            deploy: self.deploy.into_compact(registry),
+            messages: self
+                .messages
+                .into_iter()
+                .map(|msg| msg.into_compact(registry))
+                .collect::<Vec<_>>(),
+            events: self
+                .events
+                .into_iter()
+                .map(|event| event.into_compact(registry))
+                .collect::<Vec<_>>(),
+            docs: self.docs,
+        }
+    }
+}
+
+/// The message builder is ready to finalize construction.
+pub enum Valid {}
+/// The message builder is not ready to finalize construction.
+pub enum Invalid {}
+
+pub struct ContractSpecBuilder<S = Invalid> {
+    /// The name of the to-be-constructed contract specification.
+    name: <MetaForm as Form>::String,
+    /// The deploy handler of the to-be-constructed contract specification.
+    deploy: Option<DeploySpec>,
+    /// The messages of the to-be-constructed contract specification.
+    messages: Vec<MessageSpec>,
+    /// The events of the to-be-constructed contract specification.
+    events: Vec<EventSpec>,
+    /// The documentation of the to-be-constructed contract specification.
+    docs: Vec<<MetaForm as Form>::String>,
+    /// Marker for compile-time checking of valid contract specifications.
+    marker: PhantomData<fn() -> S>,
+}
+
+impl ContractSpecBuilder<Invalid> {
+    /// Sets the deploy handler of the contract specification.
+    pub fn on_deploy(self, deploy_handler: DeploySpec) -> ContractSpecBuilder<Valid> {
+        ContractSpecBuilder {
+            name: self.name,
+            deploy: Some(deploy_handler),
+            messages: self.messages,
+            events: self.events,
+            docs: self.docs,
+            marker: PhantomData,
+        }
+    }
+}
+
+impl<S> ContractSpecBuilder<S> {
+    /// Sets the messages of the contract specification.
+    pub fn messages<M>(self, messages: M) -> Self
+    where
+        M: IntoIterator<Item = MessageSpec>,
+    {
+        debug_assert!(self.messages.is_empty());
+        Self {
+            messages: messages.into_iter().collect::<Vec<_>>(),
+            ..self
+        }
+    }
+
+    /// Sets the events of the contract specification.
+    pub fn events<E>(self, events: E) -> Self
+    where
+        E: IntoIterator<Item = EventSpec>,
+    {
+        debug_assert!(self.events.is_empty());
+        Self {
+            events: events.into_iter().collect::<Vec<_>>(),
+            ..self
+        }
+    }
+
+    /// Sets the documentation of the contract specification.
+    pub fn docs<D>(self, docs: D) -> Self
+    where
+        D: IntoIterator<Item = &'static str>,
+    {
+        debug_assert!(self.docs.is_empty());
+        Self {
+            docs: docs.into_iter().collect::<Vec<_>>(),
+            ..self
+        }
+    }
+}
+
+impl ContractSpecBuilder<Valid> {
+    /// Finalizes construction of the contract specification.
+    pub fn done(self) -> ContractSpec {
+        ContractSpec {
+            name: self.name,
+            deploy: self
+                .deploy
+                .expect("a valid contract spec build must have a deploy handler; qed"),
+            messages: self.messages,
+            events: self.events,
+            docs: self.docs,
+        }
+    }
+}
+
+impl ContractSpec {
+    /// Creates a new contract specification.
+    pub fn new(name: <MetaForm as Form>::String) -> ContractSpecBuilder {
+        ContractSpecBuilder {
+            name,
+            deploy: None,
+            messages: vec![],
+            events: vec![],
+            docs: vec![],
+            marker: PhantomData,
+        }
+    }
+}
+
+/// Describes the deploy handler of a contract.
+#[derive(Debug, PartialEq, Eq, Serialize)]
+#[serde(bound = "F::TypeId: Serialize")]
+pub struct DeploySpec<F: Form = MetaForm> {
+    /// The parameters of the deploy handler.
+    args: Vec<MessageParamSpec<F>>,
+    /// The deploy handler documentation.
+    docs: Vec<&'static str>,
+}
+
+impl IntoCompact for DeploySpec {
+    type Output = DeploySpec<CompactForm>;
+
+    fn into_compact(self, registry: &mut Registry) -> Self::Output {
+        DeploySpec {
+            args: self
+                .args
+                .into_iter()
+                .map(|arg| arg.into_compact(registry))
+                .collect::<Vec<_>>(),
+            docs: self.docs,
+        }
+    }
+}
+
+impl DeploySpec {
+    /// Creates a new deploy specification builder.
+    pub fn new() -> DeploySpecBuilder {
+        DeploySpecBuilder {
+            spec: Self {
+                args: vec![],
+                docs: vec![],
+            },
+        }
+    }
+}
+
+/// A builder to construct a deploy specification.
+pub struct DeploySpecBuilder {
+    spec: DeploySpec,
+}
+
+impl DeploySpecBuilder {
+    /// Sets the input arguments of the deploy spec.
+    pub fn args<A>(self, args: A) -> DeploySpecBuilder
+    where
+        A: IntoIterator<Item = MessageParamSpec>,
+    {
+        let mut this = self;
+        debug_assert!(this.spec.args.is_empty());
+        this.spec.args = args.into_iter().collect::<Vec<_>>();
+        this
+    }
+
+    /// Sets the documentation of the deploy spec.
+    pub fn docs<D>(self, docs: D) -> DeploySpecBuilder
+    where
+        D: IntoIterator<Item = &'static str>,
+    {
+        let mut this = self;
+        debug_assert!(this.spec.docs.is_empty());
+        this.spec.docs = docs.into_iter().collect::<Vec<_>>();
+        this
+    }
+
+    /// Finishes building the deploy spec.
+    pub fn done(self) -> DeploySpec {
+        self.spec
+    }
+}
+
+/// Describes a contract message.
+#[derive(Debug, PartialEq, Eq, Serialize)]
+#[serde(bound = "F::TypeId: Serialize")]
+pub struct MessageSpec<F: Form = MetaForm> {
+    /// The name of the message.
+    name: F::String,
+    /// The selector hash of the message.
+    selector: u32,
+    /// If the message is allowed to mutate the contract state.
+    mutates: bool,
+    /// The parameters of the message.
+    args: Vec<MessageParamSpec<F>>,
+    /// The return type of the message.
+    return_type: ReturnTypeSpec<F>,
+    /// The message documentation.
+    docs: Vec<&'static str>,
+}
+
+mod state {
+    //! Type states that tell what state of a message has not
+    //! yet been set properly for a valid construction.
+
+    /// Type state for the message selector of a message.
+    pub struct Selector;
+    /// Type state for the mutability of a message.
+    pub struct Mutates;
+    /// Type state for the return type of a message.
+    pub struct Returns;
+}
+
+/// Type state for the message builder to tell that some mandatory state has not yet been set
+/// yet or to fail upon setting the same state multiple times.
+pub struct Missing<S>(PhantomData<fn() -> S>);
+
+impl MessageSpec {
+    /// Creates a new message spec builder.
+    pub fn new(
+        name: <MetaForm as Form>::String,
+    ) -> MessageSpecBuilder<
+        Missing<state::Selector>,
+        Missing<state::Mutates>,
+        Missing<state::Returns>,
+    > {
+        MessageSpecBuilder {
+            spec: Self {
+                name,
+                selector: 0,
+                mutates: false,
+                args: vec![],
+                return_type: ReturnTypeSpec::none(),
+                docs: vec![],
+            },
+            marker: PhantomData,
+        }
+    }
+}
+
+/// A builder for messages.
+///
+/// # Dev
+///
+/// Some of the fields are guarded by a type-state pattern to
+/// fail at compile-time instead of at run-time. This is useful
+/// to better debug code-gen macros.
+pub struct MessageSpecBuilder<Selector, Mutates, Returns> {
+    spec: MessageSpec,
+    marker: PhantomData<fn() -> (Selector, Mutates, Returns)>,
+}
+
+impl<M, R> MessageSpecBuilder<Missing<state::Selector>, M, R> {
+    /// Sets the function selector of the message.
+    pub fn selector(self, selector: u32) -> MessageSpecBuilder<state::Selector, M, R> {
+        MessageSpecBuilder {
+            spec: MessageSpec {
+                selector,
+                ..self.spec
+            },
+            marker: PhantomData,
+        }
+    }
+}
+
+impl<S, R> MessageSpecBuilder<S, Missing<state::Mutates>, R> {
+    /// Sets if the message is mutable, thus taking `&mut self` or not thus taking `&self`.
+    pub fn mutates(self, mutates: bool) -> MessageSpecBuilder<S, state::Mutates, R> {
+        MessageSpecBuilder {
+            spec: MessageSpec {
+                mutates,
+                ..self.spec
+            },
+            marker: PhantomData,
+        }
+    }
+}
+
+impl<M, S> MessageSpecBuilder<S, M, Missing<state::Returns>> {
+    /// Sets the return type of the message.
+    pub fn returns(
+        self,
+        return_type: ReturnTypeSpec,
+    ) -> MessageSpecBuilder<S, M, state::Returns> {
+        MessageSpecBuilder {
+            spec: MessageSpec {
+                return_type,
+                ..self.spec
+            },
+            marker: PhantomData,
+        }
+    }
+}
+
+impl<S, M, R> MessageSpecBuilder<S, M, R> {
+    /// Sets the input arguments of the message specification.
+    pub fn args<A>(self, args: A) -> Self
+    where
+        A: IntoIterator<Item = MessageParamSpec>,
+    {
+        let mut this = self;
+        debug_assert!(this.spec.args.is_empty());
+        this.spec.args = args.into_iter().collect::<Vec<_>>();
+        this
+    }
+
+    /// Sets the documentation of the message specification.
+    pub fn docs<D>(self, docs: D) -> Self
+    where
+        D: IntoIterator<Item = &'static str>,
+    {
+        let mut this = self;
+        debug_assert!(this.spec.docs.is_empty());
+        this.spec.docs = docs.into_iter().collect::<Vec<_>>();
+        this
+    }
+}
+
+impl MessageSpecBuilder<state::Selector, state::Mutates, state::Returns> {
+    /// Finishes construction of the message.
+    pub fn done(self) -> MessageSpec {
+        self.spec
+    }
+}
+
+impl IntoCompact for MessageSpec {
+    type Output = MessageSpec<CompactForm>;
+
+    fn into_compact(self, registry: &mut Registry) -> Self::Output {
+        MessageSpec {
+            name: registry.register_string(&self.name),
+            selector: self.selector,
+            mutates: self.mutates,
+            args: self
+                .args
+                .into_iter()
+                .map(|arg| arg.into_compact(registry))
+                .collect::<Vec<_>>(),
+            return_type: self.return_type.into_compact(registry),
+            docs: self.docs,
+        }
+    }
+}
+
+/// Describes an event definition.
+#[derive(Debug, PartialEq, Eq, Serialize)]
+#[serde(bound = "F::TypeId: Serialize")]
+pub struct EventSpec<F: Form = MetaForm> {
+    /// The name of the event.
+    name: F::String,
+    /// The event arguments.
+    args: Vec<EventParamSpec<F>>,
+    /// The event documentation.
+    docs: Vec<&'static str>,
+}
+
+/// An event specification builder.
+pub struct EventSpecBuilder {
+    spec: EventSpec,
+}
+
+impl EventSpecBuilder {
+    /// Sets the input arguments of the event specification.
+    pub fn args<A>(self, args: A) -> Self
+    where
+        A: IntoIterator<Item = EventParamSpec>,
+    {
+        let mut this = self;
+        debug_assert!(this.spec.args.is_empty());
+        this.spec.args = args.into_iter().collect::<Vec<_>>();
+        this
+    }
+
+    /// Sets the input arguments of the event specification.
+    pub fn docs<D>(self, docs: D) -> Self
+    where
+        D: IntoIterator<Item = &'static str>,
+    {
+        let mut this = self;
+        debug_assert!(this.spec.docs.is_empty());
+        this.spec.docs = docs.into_iter().collect::<Vec<_>>();
+        this
+    }
+
+    /// Finalizes building the event specification.
+    pub fn done(self) -> EventSpec {
+        self.spec
+    }
+}
+
+impl IntoCompact for EventSpec {
+    type Output = EventSpec<CompactForm>;
+
+    fn into_compact(self, registry: &mut Registry) -> Self::Output {
+        EventSpec {
+            name: registry.register_string(&self.name),
+            args: self
+                .args
+                .into_iter()
+                .map(|arg| arg.into_compact(registry))
+                .collect::<Vec<_>>(),
+            docs: self.docs,
+        }
+    }
+}
+
+impl EventSpec {
+    /// Creates a new event specification builder.
+    pub fn new(name: &'static str) -> EventSpecBuilder {
+        EventSpecBuilder {
+            spec: Self {
+                name,
+                args: vec![],
+                docs: vec![],
+            },
+        }
+    }
+}
+
+/// Describes a pair of parameter name and type.
+#[derive(Debug, PartialEq, Eq, Serialize)]
+#[serde(bound = "F::TypeId: Serialize")]
+pub struct EventParamSpec<F: Form = MetaForm> {
+    /// The name of the parameter.
+    name: F::String,
+    /// If the event parameter is indexed.
+    indexed: bool,
+    /// The type of the parameter.
+    #[serde(rename = "type")]
+    ty: F::TypeId,
+}
+
+impl IntoCompact for EventParamSpec {
+    type Output = EventParamSpec<CompactForm>;
+
+    fn into_compact(self, registry: &mut Registry) -> Self::Output {
+        EventParamSpec {
+            name: registry.register_string(self.name),
+            indexed: self.indexed,
+            ty: registry.register_type(&self.ty),
+        }
+    }
+}
+
+impl EventParamSpec {
+    /// Creates a new event parameter specification.
+    pub fn new<T>(name: &'static str, indexed: bool) -> Self
+    where
+        T: Metadata,
+    {
+        Self {
+            name,
+            indexed,
+            ty: T::meta_type(),
+        }
+    }
+
+    /// Creates a new event parameter specification.
+    pub fn of<T>(name: &'static str, _ty: &T, indexed: bool) -> Self
+    where
+        T: Metadata,
+    {
+        Self::new::<T>(name, indexed)
+    }
+}
+
+/// Describes the return type of a contract message.
+#[derive(Debug, PartialEq, Eq, Serialize)]
+#[serde(transparent)]
+#[serde(bound = "F::TypeId: Serialize")]
+pub struct ReturnTypeSpec<F: Form = MetaForm> {
+    #[serde(rename = "type")]
+    opt_type: Option<F::TypeId>,
+}
+
+impl IntoCompact for ReturnTypeSpec {
+    type Output = ReturnTypeSpec<CompactForm>;
+
+    fn into_compact(self, registry: &mut Registry) -> Self::Output {
+        ReturnTypeSpec {
+            opt_type: self.opt_type.map(|opt_ty| registry.register_type(&opt_ty)),
+        }
+    }
+}
+
+impl ReturnTypeSpec {
+    /// Creates a new return type specification indicating no return type.
+    pub fn none() -> Self {
+        Self { opt_type: None }
+    }
+
+    /// Creates a new return type specification for the given type.
+    pub fn new<T>() -> Self
+    where
+        T: Metadata,
+    {
+        Self {
+            opt_type: Some(T::meta_type()),
+        }
+    }
+
+    /// Creates a new return type specification for the given type.
+    pub fn of<T>(_ty: &T) -> Self
+    where
+        T: Metadata,
+    {
+        Self::new::<T>()
+    }
+}
+
+/// Describes a pair of parameter name and type.
+#[derive(Debug, PartialEq, Eq, Serialize)]
+#[serde(bound = "F::TypeId: Serialize")]
+pub struct MessageParamSpec<F: Form = MetaForm> {
+    /// The name of the parameter.
+    name: F::String,
+    /// The type of the parameter.
+    #[serde(rename = "type")]
+    ty: F::TypeId,
+}
+
+impl IntoCompact for MessageParamSpec {
+    type Output = MessageParamSpec<CompactForm>;
+
+    fn into_compact(self, registry: &mut Registry) -> Self::Output {
+        MessageParamSpec {
+            name: registry.register_string(self.name),
+            ty: registry.register_type(&self.ty),
+        }
+    }
+}
+
+impl MessageParamSpec {
+    /// Creates a new parameter specification for the given name and type.
+    pub fn new<T>(name: &'static str) -> Self
+    where
+        T: Metadata,
+    {
+        Self {
+            name,
+            ty: T::meta_type(),
+        }
+    }
+
+    /// Creates a new parameter specification for the given name and type.
+    pub fn of<T>(name: &'static str, _ty: &T) -> Self
+    where
+        T: Metadata,
+    {
+        Self::new::<T>(name)
+    }
+}
diff --git a/cli/template/.ink/abi_gen/Cargo.toml b/cli/template/.ink/abi_gen/Cargo.toml
new file mode 100644
index 0000000000..e7985acaf9
--- /dev/null
+++ b/cli/template/.ink/abi_gen/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "abi-gen"
+version = "0.1.0"
+authors = ["Parity Technologies <admin@parity.io>"]
+edition = "2018"
+publish = false
+
+[[bin]]
+name = "abi-gen"
+path = "main.rs"
+
+[dependencies]
+contract = { path = "../..", package = "{{name}}", features = ["ink-generate-abi", "test-env"] }
+serde = { version = "1.0", features = ["derive"] }
+serde_json = "1.0"
diff --git a/cli/template/.ink/abi_gen/main.rs b/cli/template/.ink/abi_gen/main.rs
new file mode 100644
index 0000000000..576885b191
--- /dev/null
+++ b/cli/template/.ink/abi_gen/main.rs
@@ -0,0 +1,8 @@
+
+fn main() -> Result<(), std::io::Error> {
+    let abi = contract::ink_generate_abi();
+    let contents = serde_json::to_string_pretty(&abi)?;
+    std::fs::create_dir("target").ok();
+    std::fs::write("target/abi.json", contents)?;
+    Ok(())
+}
diff --git a/cli/template/Cargo.toml b/cli/template/Cargo.toml
index ba613765d4..8fad03cdc6 100644
--- a/cli/template/Cargo.toml
+++ b/cli/template/Cargo.toml
@@ -5,22 +5,33 @@ authors = ["[your_name] <[your_email]>"]
 edition = "2018"
 
 [dependencies]
+ink_abi = { git = "https://github.com/paritytech/ink", package = "ink_abi", default-features = false, optional = true }
 ink_core = { git = "https://github.com/paritytech/ink", package = "ink_core", default-features = false }
 ink_model = { git = "https://github.com/paritytech/ink", package = "ink_model", default-features = false }
 ink_lang = { git = "https://github.com/paritytech/ink", package = "ink_lang", default-features = false }
+
 scale = { package = "parity-scale-codec", version = "1.0", default-features = false, features = ["derive"] }
+serde = { version = "1.0", default-features = false, features = ["derive"], optional = true }
+type-metadata = { git = "https://github.com/type-metadata/type-metadata.git", default-features = false, features = ["derive"], optional = true }
 
 [lib]
 name = "{{name}}"
-crate-type = ["cdylib"]
+crate-type = [
+    # Used for normal contract Wasm blobs.
+    "cdylib",
+    # Used for ABI generation.
+    "rlib",
+]
 
 [features]
-default = ["std"]
+default = ["test-env"]
 std = [
+    "ink_abi/std",
     "ink_core/std",
     "ink_model/std",
     "ink_lang/std",
     "scale/std",
+    "type-metadata/std",
 ]
 test-env = [
     "std",
@@ -28,8 +39,13 @@ test-env = [
     "ink_model/test-env",
     "ink_lang/test-env",
 ]
-generate-api-description = [
-    "ink_lang/generate-api-description"
+ink-generate-abi = [
+    "std",
+    "ink_abi",
+    "serde",
+    "serde_json",
+    "type-metadata",
+    "ink_core/ink-generate-abi",
 ]
 
 [profile.release]
@@ -37,3 +53,11 @@ panic = "abort"
 lto = true
 opt-level = "z"
 overflow-checks = true
+
+[workspace]
+members = [
+	".ink/abi_gen"
+]
+exclude = [
+	".ink"
+]
diff --git a/cli/template/build.sh b/cli/template/build.sh
index de80fceecd..c58eda0567 100644
--- a/cli/template/build.sh
+++ b/cli/template/build.sh
@@ -7,7 +7,7 @@ PROJNAME={{name}}
 # rm Cargo.lock
 
 CARGO_INCREMENTAL=0 &&
-cargo +nightly build --no-default-features --release --features generate-api-description --target=wasm32-unknown-unknown --verbose
+cargo +nightly build --no-default-features --release --target=wasm32-unknown-unknown --verbose
 wasm2wat -o target/$PROJNAME.wat target/wasm32-unknown-unknown/release/$PROJNAME.wasm
 cat target/$PROJNAME.wat | sed "s/(import \"env\" \"memory\" (memory (;0;) 2))/(import \"env\" \"memory\" (memory (;0;) 2 16))/" > target/$PROJNAME-fixed.wat
 wat2wasm -o target/$PROJNAME.wasm target/$PROJNAME-fixed.wat
diff --git a/cli/template/src/lib.rs b/cli/template/src/lib.rs
index 0531f7e0c0..d87044d524 100644
--- a/cli/template/src/lib.rs
+++ b/cli/template/src/lib.rs
@@ -1,4 +1,4 @@
-#![cfg_attr(not(any(test, feature = "std")), no_std)]
+#![cfg_attr(not(feature = "std"), no_std)]
 
 use ink_core::{
     memory::format,
diff --git a/core/Cargo.toml b/core/Cargo.toml
index ae57336d17..e7b9ff6c7e 100644
--- a/core/Cargo.toml
+++ b/core/Cargo.toml
@@ -18,15 +18,25 @@ categories = ["no-std", "embedded"]
 include = ["/Cargo.toml", "src/**/*.rs", "/README.md", "/LICENSE"]
 
 [dependencies]
+ink_abi = { path = "../abi/", default-features = false, features = ["derive"], optional = true }
 ink_alloc = { path = "../alloc/", default-features = false }
 ink_utils = { path = "../utils/", default-features = false }
 scale = { package = "parity-scale-codec", version = "1.0", default-features = false, features = ["derive", "full"] }
 
+type-metadata = { git = "https://github.com/type-metadata/type-metadata.git", default-features = false, features = ["derive"], optional = true }
+
 [features]
 default = ["std"]
 test-env = ["std"]
 std = [
+    "ink_abi/std",
     "ink_alloc/std",
     "ink_utils/std",
     "scale/std",
+    "type-metadata/std",
 ]
+ink-generate-abi = [
+    "ink_abi",
+    "type-metadata",
+    "std",
+]
\ No newline at end of file
diff --git a/core/src/env/srml/types.rs b/core/src/env/srml/types.rs
index b863196f8e..f5baf661f5 100644
--- a/core/src/env/srml/types.rs
+++ b/core/src/env/srml/types.rs
@@ -28,6 +28,8 @@ use scale::{
     Decode,
     Encode,
 };
+#[cfg(feature = "ink-generate-abi")]
+use type_metadata::Metadata;
 
 /// The SRML fundamental types.
 #[allow(unused)]
@@ -67,6 +69,7 @@ impl EnvTypes for DefaultSrmlTypes {
 
 /// The default SRML address type.
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Encode, Decode)]
+#[cfg_attr(feature = "ink-generate-abi", derive(Metadata))]
 pub struct AccountId([u8; 32]);
 
 impl From<[u8; 32]> for AccountId {
@@ -89,6 +92,7 @@ pub type Balance = u128;
 
 /// The default SRML hash type.
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Encode, Decode)]
+#[cfg_attr(feature = "ink-generate-abi", derive(Metadata))]
 pub struct Hash([u8; 32]);
 
 impl From<[u8; 32]> for Hash {
diff --git a/core/src/storage/alloc/bump_alloc.rs b/core/src/storage/alloc/bump_alloc.rs
index 25e4f41917..0a147eb39d 100644
--- a/core/src/storage/alloc/bump_alloc.rs
+++ b/core/src/storage/alloc/bump_alloc.rs
@@ -17,6 +17,8 @@
 use super::*;
 
 use crate::storage::Key;
+#[cfg(feature = "ink-generate-abi")]
+use type_metadata::Metadata;
 
 /// An allocator that is meant to allocate contract storage at
 /// compile-time by simply bumping its current allocation key.
@@ -28,6 +30,7 @@ use crate::storage::Key;
 ///
 /// Users are recommended to use the [`CellChunkAlloc`](struct.CellChunkAlloc.html)
 /// for dynamic storage allocation purposes instead.
+#[cfg_attr(feature = "ink-generate-abi", derive(Metadata))]
 pub struct BumpAlloc {
     /// The key offset used for all allocations.
     offset_key: Key,
diff --git a/core/src/storage/alloc/cc_alloc.rs b/core/src/storage/alloc/cc_alloc.rs
index c7f2cda838..d158c1c145 100644
--- a/core/src/storage/alloc/cc_alloc.rs
+++ b/core/src/storage/alloc/cc_alloc.rs
@@ -20,11 +20,19 @@ use crate::storage::{
     Flush,
     Key,
 };
-
+#[cfg(feature = "ink-generate-abi")]
+use ink_abi::{
+    HasLayout,
+    LayoutField,
+    LayoutStruct,
+    StorageLayout,
+};
 use scale::{
     Decode,
     Encode,
 };
+#[cfg(feature = "ink-generate-abi")]
+use type_metadata::Metadata;
 
 /// An allocator for the contract storage.
 ///
@@ -39,6 +47,7 @@ use scale::{
 ///
 /// Allocating and deallocating are always O(1) operations.
 #[derive(Debug, Encode, Decode)]
+#[cfg_attr(feature = "ink-generate-abi", derive(Metadata))]
 pub struct CellChunkAlloc {
     /// Allocator stash for single cells.
     cells: storage::Stash<()>,
@@ -98,6 +107,20 @@ impl Flush for CellChunkAlloc {
     }
 }
 
+#[cfg(feature = "ink-generate-abi")]
+impl HasLayout for CellChunkAlloc {
+    fn layout(&self) -> StorageLayout {
+        LayoutStruct::new(
+            Self::meta_type(),
+            vec![
+                LayoutField::of("cells", &self.cells),
+                LayoutField::of("chunks", &self.chunks),
+            ],
+        )
+        .into()
+    }
+}
+
 impl CellChunkAlloc {
     /// Returns the key to the first cell allocation.
     ///
diff --git a/core/src/storage/alloc/dyn_alloc.rs b/core/src/storage/alloc/dyn_alloc.rs
index 2a1d7fce22..fa8175fde1 100644
--- a/core/src/storage/alloc/dyn_alloc.rs
+++ b/core/src/storage/alloc/dyn_alloc.rs
@@ -25,6 +25,15 @@ use crate::storage::{
     Flush,
     Key,
 };
+#[cfg(feature = "ink-generate-abi")]
+use ink_abi::{
+    HasLayout,
+    LayoutField,
+    LayoutStruct,
+    StorageLayout,
+};
+#[cfg(feature = "ink-generate-abi")]
+use type_metadata::Metadata;
 
 /// Allocator for dynamic contract storage.
 ///
@@ -34,6 +43,7 @@ use crate::storage::{
 /// at the same time. This is subject to change in the future if
 /// experiments show that this is a bottle neck.
 #[derive(Debug)]
+#[cfg_attr(feature = "ink-generate-abi", derive(Metadata))]
 pub struct DynAlloc {
     /// Bitmap indicating free cell slots.
     free_cells: storage::BitVec,
@@ -45,6 +55,22 @@ pub struct DynAlloc {
     chunks_origin: Key,
 }
 
+#[cfg(feature = "ink-generate-abi")]
+impl HasLayout for DynAlloc {
+    fn layout(&self) -> StorageLayout {
+        LayoutStruct::new(
+            Self::meta_type(),
+            vec![
+                LayoutField::of("free_cells", &self.free_cells),
+                LayoutField::of("free_chunks", &self.free_cells),
+                LayoutField::of("cells_origin", &self.cells_origin),
+                LayoutField::of("chunks_origin", &self.chunks_origin),
+            ],
+        )
+        .into()
+    }
+}
+
 impl AllocateUsing for DynAlloc {
     unsafe fn allocate_using<A>(alloc: &mut A) -> Self
     where
@@ -75,7 +101,7 @@ impl Flush for DynAlloc {
     }
 }
 
-#[cfg(test)]
+#[cfg(all(test, feature = "test-env"))]
 impl DynAlloc {
     pub(crate) fn cells_origin(&self) -> Key {
         self.cells_origin
diff --git a/core/src/storage/cell/raw_cell.rs b/core/src/storage/cell/raw_cell.rs
index e4458b3387..100ab5d9aa 100644
--- a/core/src/storage/cell/raw_cell.rs
+++ b/core/src/storage/cell/raw_cell.rs
@@ -76,6 +76,11 @@ impl RawCell {
     pub fn clear(&mut self) {
         unsafe { env::clear(self.key) }
     }
+
+    /// Returns the associated, internal raw key.
+    pub fn raw_key(&self) -> Key {
+        self.key
+    }
 }
 
 #[cfg(all(test, feature = "test-env"))]
diff --git a/core/src/storage/cell/sync_cell.rs b/core/src/storage/cell/sync_cell.rs
index 2a150f8a4c..65b4c0a8b2 100644
--- a/core/src/storage/cell/sync_cell.rs
+++ b/core/src/storage/cell/sync_cell.rs
@@ -23,10 +23,25 @@ use crate::{
         },
         cell::TypedCell,
         Flush,
+        Key,
     },
 };
-
 use core::cell::RefCell;
+#[cfg(feature = "ink-generate-abi")]
+use ink_abi::{
+    HasLayout,
+    LayoutRange,
+    StorageLayout,
+};
+#[cfg(feature = "ink-generate-abi")]
+use type_metadata::{
+    HasTypeDef,
+    Metadata,
+    NamedField,
+    TypeDef,
+    TypeDefStruct,
+    TypeId,
+};
 
 /// A synchronized cell.
 ///
@@ -39,6 +54,7 @@ use core::cell::RefCell;
 ///
 /// Read more about kinds of guarantees and their effect [here](../index.html#guarantees).
 #[derive(Debug)]
+#[cfg_attr(feature = "ink-generate-abi", derive(TypeId))]
 pub struct SyncCell<T> {
     /// The underlying typed cell.
     cell: TypedCell<T>,
@@ -46,6 +62,13 @@ pub struct SyncCell<T> {
     cache: Cache<T>,
 }
 
+#[cfg(feature = "ink-generate-abi")]
+impl<T> HasTypeDef for SyncCell<T> {
+    fn type_def() -> TypeDef {
+        TypeDefStruct::new(vec![NamedField::of::<Key>("cell")]).into()
+    }
+}
+
 /// A synchronized cache entry.
 #[derive(Debug)]
 pub struct SyncCacheEntry<T> {
@@ -286,6 +309,16 @@ impl<T> Cache<T> {
     }
 }
 
+#[cfg(feature = "ink-generate-abi")]
+impl<T> HasLayout for SyncCell<T>
+where
+    T: Metadata,
+{
+    fn layout(&self) -> StorageLayout {
+        LayoutRange::cell(self.raw_key(), T::meta_type()).into()
+    }
+}
+
 impl<T> scale::Encode for SyncCell<T> {
     fn encode_to<W: scale::Output>(&self, dest: &mut W) {
         self.cell.encode_to(dest)
@@ -339,6 +372,11 @@ impl<T> SyncCell<T> {
         self.cache.update(None);
         self.cache.mark_dirty();
     }
+
+    /// Returns the associated, internal raw key.
+    pub fn raw_key(&self) -> Key {
+        self.cell.raw_key()
+    }
 }
 
 impl<T> SyncCell<T>
diff --git a/core/src/storage/cell/typed_cell.rs b/core/src/storage/cell/typed_cell.rs
index 2e9839839b..f8f3ab0857 100644
--- a/core/src/storage/cell/typed_cell.rs
+++ b/core/src/storage/cell/typed_cell.rs
@@ -20,6 +20,7 @@ use crate::storage::{
         AllocateUsing,
     },
     cell::RawCell,
+    Key,
     NonCloneMarker,
 };
 
@@ -75,6 +76,11 @@ impl<T> TypedCell<T> {
     pub fn clear(&mut self) {
         self.cell.clear()
     }
+
+    /// Returns the associated, internal raw key.
+    pub fn raw_key(&self) -> Key {
+        self.cell.raw_key()
+    }
 }
 
 impl<T> TypedCell<T>
diff --git a/core/src/storage/chunk/sync_chunk/chunk.rs b/core/src/storage/chunk/sync_chunk/chunk.rs
index f42e142e11..948d0284e6 100644
--- a/core/src/storage/chunk/sync_chunk/chunk.rs
+++ b/core/src/storage/chunk/sync_chunk/chunk.rs
@@ -24,6 +24,21 @@ use crate::storage::{
     Flush,
     Key,
 };
+#[cfg(feature = "ink-generate-abi")]
+use ink_abi::{
+    HasLayout,
+    LayoutRange,
+    StorageLayout,
+};
+#[cfg(feature = "ink-generate-abi")]
+use type_metadata::{
+    HasTypeDef,
+    Metadata,
+    NamedField,
+    TypeDef,
+    TypeDefStruct,
+    TypeId,
+};
 
 /// A chunk of synchronized cells.
 ///
@@ -38,6 +53,7 @@ use crate::storage::{
 ///
 /// Read more about kinds of guarantees and their effect [here](../index.html#guarantees).
 #[derive(Debug)]
+#[cfg_attr(feature = "ink-generate-abi", derive(TypeId))]
 pub struct SyncChunk<T> {
     /// The underlying chunk of cells.
     chunk: TypedChunk<T>,
@@ -45,6 +61,13 @@ pub struct SyncChunk<T> {
     cache: CacheGuard<T>,
 }
 
+#[cfg(feature = "ink-generate-abi")]
+impl<T> HasTypeDef for SyncChunk<T> {
+    fn type_def() -> TypeDef {
+        TypeDefStruct::new(vec![NamedField::of::<Key>("cells_key")]).into()
+    }
+}
+
 impl<T> Flush for SyncChunk<T>
 where
     T: scale::Encode + Flush,
@@ -80,6 +103,16 @@ impl<T> scale::Decode for SyncChunk<T> {
     }
 }
 
+#[cfg(feature = "ink-generate-abi")]
+impl<T> HasLayout for SyncChunk<T>
+where
+    T: Metadata,
+{
+    fn layout(&self) -> StorageLayout {
+        LayoutRange::chunk(self.cells_key(), T::meta_type()).into()
+    }
+}
+
 impl<T> AllocateUsing for SyncChunk<T> {
     unsafe fn allocate_using<A>(alloc: &mut A) -> Self
     where
diff --git a/core/src/storage/collections/bitvec/block.rs b/core/src/storage/collections/bitvec/block.rs
index 26b62162cf..4a4ed8b0fa 100644
--- a/core/src/storage/collections/bitvec/block.rs
+++ b/core/src/storage/collections/bitvec/block.rs
@@ -20,9 +20,12 @@ use scale::{
     Decode,
     Encode,
 };
+#[cfg(feature = "ink-generate-abi")]
+use type_metadata::Metadata;
 
 /// A block of 1024 bits.
 #[derive(Debug, Copy, Clone, Encode, Decode)]
+#[cfg_attr(feature = "ink-generate-abi", derive(Metadata))]
 pub struct BitBlock {
     /// The underlying bit packs.
     packs: [BitPack; Self::PACKS as usize],
diff --git a/core/src/storage/collections/bitvec/pack.rs b/core/src/storage/collections/bitvec/pack.rs
index 53000ac3c3..fd5dfb3e01 100644
--- a/core/src/storage/collections/bitvec/pack.rs
+++ b/core/src/storage/collections/bitvec/pack.rs
@@ -18,12 +18,15 @@ use scale::{
     Decode,
     Encode,
 };
+#[cfg(feature = "ink-generate-abi")]
+use type_metadata::Metadata;
 
 /// The underlying representation type for a pack.
 pub type BitPackRepr = u32;
 
 /// A pack of 32 bits.
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Encode, Decode)]
+#[cfg_attr(feature = "ink-generate-abi", derive(Metadata))]
 #[repr(transparent)]
 pub struct BitPack {
     /// The actual bits.
diff --git a/core/src/storage/collections/bitvec/vec.rs b/core/src/storage/collections/bitvec/vec.rs
index aba406a16d..7e969054ea 100644
--- a/core/src/storage/collections/bitvec/vec.rs
+++ b/core/src/storage/collections/bitvec/vec.rs
@@ -25,9 +25,19 @@ use crate::storage::{
     chunk::SyncChunk,
     Flush,
 };
+#[cfg(feature = "ink-generate-abi")]
+use ink_abi::{
+    HasLayout,
+    LayoutField,
+    LayoutStruct,
+    StorageLayout,
+};
+#[cfg(feature = "ink-generate-abi")]
+use type_metadata::Metadata;
 
 /// A space-efficient contiguous growable bit array type.
 #[derive(Debug)]
+#[cfg_attr(feature = "ink-generate-abi", derive(Metadata))]
 pub struct BitVec {
     /// The number of bits.
     len: storage::Value<u32>,
@@ -35,6 +45,20 @@ pub struct BitVec {
     blocks: SyncChunk<BitBlock>,
 }
 
+#[cfg(feature = "ink-generate-abi")]
+impl HasLayout for BitVec {
+    fn layout(&self) -> StorageLayout {
+        LayoutStruct::new(
+            Self::meta_type(),
+            vec![
+                LayoutField::of("len", &self.len),
+                LayoutField::of("blocks", &self.blocks),
+            ],
+        )
+        .into()
+    }
+}
+
 impl scale::Encode for BitVec {
     fn encode_to<W: scale::Output>(&self, dest: &mut W) {
         self.len.encode_to(dest);
diff --git a/core/src/storage/collections/hash_map/impls.rs b/core/src/storage/collections/hash_map/impls.rs
index 67b6ddeaa1..06cbf1c245 100644
--- a/core/src/storage/collections/hash_map/impls.rs
+++ b/core/src/storage/collections/hash_map/impls.rs
@@ -32,7 +32,16 @@ use core::{
     borrow::Borrow,
     hash::Hash,
 };
+#[cfg(feature = "ink-generate-abi")]
+use ink_abi::{
+    HasLayout,
+    LayoutField,
+    LayoutStruct,
+    StorageLayout,
+};
 use ink_utils::hash;
+#[cfg(feature = "ink-generate-abi")]
+use type_metadata::Metadata;
 
 /// Mapping stored in the contract storage.
 ///
@@ -52,6 +61,7 @@ use ink_utils::hash;
 ///
 /// This distinction is important for the quadratic map probing.
 #[derive(Debug)]
+#[cfg_attr(feature = "ink-generate-abi", derive(Metadata))]
 pub struct HashMap<K, V> {
     /// The storage key to the length of this storage map.
     len: storage::Value<u32>,
@@ -64,12 +74,31 @@ pub struct HashMap<K, V> {
     entries: SyncChunk<Entry<K, V>>,
 }
 
+#[cfg(feature = "ink-generate-abi")]
+impl<K, V> HasLayout for HashMap<K, V>
+where
+    K: Metadata + 'static,
+    V: Metadata + 'static,
+{
+    fn layout(&self) -> StorageLayout {
+        LayoutStruct::new(
+            Self::meta_type(),
+            vec![
+                LayoutField::of("len", &self.len),
+                LayoutField::of("entries", &self.entries),
+            ],
+        )
+        .into()
+    }
+}
+
 /// An entry of a storage map.
 ///
 /// This can either store the entries key and value
 /// or represent an entry that was removed after it
 /// has been occupied with key and value.
 #[derive(Debug, Clone, PartialEq, Eq, scale::Encode, scale::Decode)]
+#[cfg_attr(feature = "ink-generate-abi", derive(Metadata))]
 pub enum Entry<K, V> {
     /// An occupied slot with a key and a value.
     Occupied(OccupiedEntry<K, V>),
@@ -92,6 +121,7 @@ where
 
 /// An occupied entry of a storage map.
 #[derive(Debug, Clone, PartialEq, Eq, scale::Encode, scale::Decode)]
+#[cfg_attr(feature = "ink-generate-abi", derive(Metadata))]
 pub struct OccupiedEntry<K, V> {
     /// The entry's key.
     key: K,
diff --git a/core/src/storage/collections/stash/impls.rs b/core/src/storage/collections/stash/impls.rs
index f95b0d86d2..cb394b0b3a 100644
--- a/core/src/storage/collections/stash/impls.rs
+++ b/core/src/storage/collections/stash/impls.rs
@@ -25,11 +25,19 @@ use crate::storage::{
     Flush,
     Key,
 };
-
+#[cfg(feature = "ink-generate-abi")]
+use ink_abi::{
+    HasLayout,
+    LayoutField,
+    LayoutStruct,
+    StorageLayout,
+};
 use scale::{
     Decode,
     Encode,
 };
+#[cfg(feature = "ink-generate-abi")]
+use type_metadata::Metadata;
 
 /// A stash collection.
 ///
@@ -52,6 +60,7 @@ use scale::{
 /// 3. Except the guarantees noted above, you can assume nothing about key
 ///    assignment or iteration order. They can change at any time.
 #[derive(Debug)]
+#[cfg_attr(feature = "ink-generate-abi", derive(Metadata))]
 pub struct Stash<T> {
     /// Stores densly packed general stash information.
     header: storage::Value<StashHeader>,
@@ -67,7 +76,8 @@ pub struct Stash<T> {
 /// for performance reasons so that they all reside in the same
 /// storage entiry. This allows implementations to perform less reads
 /// and writes to the underlying contract storage.
-#[derive(Debug, scale::Encode, scale::Decode)]
+#[derive(Debug, Encode, Decode)]
+#[cfg_attr(feature = "ink-generate-abi", derive(Metadata))]
 struct StashHeader {
     /// The latest vacant index.
     next_vacant: u32,
@@ -114,6 +124,23 @@ where
     }
 }
 
+#[cfg(feature = "ink-generate-abi")]
+impl<T> HasLayout for Stash<T>
+where
+    T: Metadata + 'static,
+{
+    fn layout(&self) -> StorageLayout {
+        LayoutStruct::new(
+            Self::meta_type(),
+            vec![
+                LayoutField::of("header", &self.header),
+                LayoutField::of("entries", &self.entries),
+            ],
+        )
+        .into()
+    }
+}
+
 impl<'a, T> Iterator for Values<'a, T>
 where
     T: scale::Codec,
@@ -223,6 +250,7 @@ where
 /// This represents either an occupied entry with its associated value
 /// or a vacant entry pointing to the next vacant entry.
 #[derive(Debug, Encode, Decode)]
+#[cfg_attr(feature = "ink-generate-abi", derive(Metadata))]
 enum Entry<T> {
     /// A vacant entry pointing to the next vacant index.
     Vacant(u32),
diff --git a/core/src/storage/collections/vec/impls.rs b/core/src/storage/collections/vec/impls.rs
index 9fa151c874..937362aff5 100644
--- a/core/src/storage/collections/vec/impls.rs
+++ b/core/src/storage/collections/vec/impls.rs
@@ -24,11 +24,19 @@ use crate::storage::{
     chunk::SyncChunk,
     Flush,
 };
-
 use core::iter::{
     DoubleEndedIterator,
     ExactSizeIterator,
 };
+#[cfg(feature = "ink-generate-abi")]
+use ink_abi::{
+    HasLayout,
+    LayoutField,
+    LayoutStruct,
+    StorageLayout,
+};
+#[cfg(feature = "ink-generate-abi")]
+use type_metadata::Metadata;
 
 /// A contiguous growable array type, written `Vec<T>` but pronounced 'vector'.
 ///
@@ -45,6 +53,7 @@ use core::iter::{
 /// Allows to store up to `2^32` elements and is guaranteed to not reallocate
 /// upon pushing new elements to it.
 #[derive(Debug)]
+#[cfg_attr(feature = "ink-generate-abi", derive(Metadata))]
 pub struct Vec<T> {
     /// The length of the vector.
     len: storage::Value<u32>,
@@ -84,6 +93,23 @@ where
     }
 }
 
+#[cfg(feature = "ink-generate-abi")]
+impl<T> HasLayout for Vec<T>
+where
+    T: Metadata + 'static,
+{
+    fn layout(&self) -> StorageLayout {
+        LayoutStruct::new(
+            Self::meta_type(),
+            vec![
+                LayoutField::of("len", &self.len),
+                LayoutField::of("cells", &self.cells),
+            ],
+        )
+        .into()
+    }
+}
+
 impl<'a, T> Iterator for Iter<'a, T>
 where
     T: scale::Codec,
diff --git a/core/src/storage/key.rs b/core/src/storage/key.rs
index c33c67224c..a4a4811219 100644
--- a/core/src/storage/key.rs
+++ b/core/src/storage/key.rs
@@ -15,10 +15,19 @@
 // along with ink!.  If not, see <http://www.gnu.org/licenses/>.
 
 use crate::byte_utils;
+#[cfg(feature = "ink-generate-abi")]
+use ink_abi::{
+    HasLayout,
+    LayoutKey,
+    LayoutRange,
+    StorageLayout,
+};
 use scale::{
     Decode,
     Encode,
 };
+#[cfg(feature = "ink-generate-abi")]
+use type_metadata::Metadata;
 
 /// Typeless generic key into contract storage.
 ///
@@ -37,8 +46,16 @@ use scale::{
 ///
 /// Prefer using types found in `collections` or `Synced` type.
 #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Encode, Decode)]
+#[cfg_attr(feature = "ink-generate-abi", derive(Metadata))]
 pub struct Key(pub [u8; 32]);
 
+#[cfg(feature = "ink-generate-abi")]
+impl HasLayout for Key {
+    fn layout(&self) -> StorageLayout {
+        LayoutRange::cell(*self, <[u8]>::meta_type()).into()
+    }
+}
+
 impl core::fmt::Debug for Key {
     fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
         write!(f, "Key(")?;
@@ -77,6 +94,13 @@ impl core::fmt::Display for Key {
     }
 }
 
+#[cfg(feature = "ink-generate-abi")]
+impl From<Key> for LayoutKey {
+    fn from(key: Key) -> Self {
+        LayoutKey(key.0)
+    }
+}
+
 impl Key {
     /// Returns the byte slice of this key.
     pub fn as_bytes(&self) -> &[u8] {
diff --git a/core/src/storage/value.rs b/core/src/storage/value.rs
index 104f22bbd8..d65785993f 100644
--- a/core/src/storage/value.rs
+++ b/core/src/storage/value.rs
@@ -24,10 +24,19 @@ use crate::storage::{
     cell::SyncCell,
     Flush,
 };
+#[cfg(feature = "ink-generate-abi")]
+use ink_abi::{
+    HasLayout,
+    LayoutField,
+    LayoutStruct,
+    StorageLayout,
+};
 use scale::{
     Decode,
     Encode,
 };
+#[cfg(feature = "ink-generate-abi")]
+use type_metadata::Metadata;
 
 // Missing traits:
 //
@@ -49,11 +58,23 @@ use scale::{
 /// [`set`](struct.Value.html#method.set) or
 /// [`mutate_with`](struct.Value.html#method.mutate_with).
 #[derive(Debug, Encode, Decode)]
+#[cfg_attr(feature = "ink-generate-abi", derive(Metadata))]
 pub struct Value<T> {
     /// The cell of the storage value.
     cell: SyncCell<T>,
 }
 
+#[cfg(feature = "ink-generate-abi")]
+impl<T> HasLayout for Value<T>
+where
+    T: Metadata + 'static,
+{
+    fn layout(&self) -> StorageLayout {
+        LayoutStruct::new(Self::meta_type(), vec![LayoutField::of("cell", &self.cell)])
+            .into()
+    }
+}
+
 impl<T> AllocateUsing for Value<T> {
     unsafe fn allocate_using<A>(alloc: &mut A) -> Self
     where
diff --git a/examples/lang/erc20/.ink/abi_gen/Cargo.toml b/examples/lang/erc20/.ink/abi_gen/Cargo.toml
new file mode 100644
index 0000000000..0e566c9df5
--- /dev/null
+++ b/examples/lang/erc20/.ink/abi_gen/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "abi-gen"
+version = "0.1.0"
+authors = ["Parity Technologies <admin@parity.io>"]
+edition = "2018"
+publish = false
+
+[[bin]]
+name = "abi-gen"
+path = "main.rs"
+
+[dependencies]
+contract = { path = "../..", package = "erc20", features = ["ink-generate-abi"] }
+serde = { version = "1.0", features = ["derive"] }
+serde_json = "1.0"
diff --git a/examples/lang/erc20/.ink/abi_gen/main.rs b/examples/lang/erc20/.ink/abi_gen/main.rs
new file mode 100644
index 0000000000..576885b191
--- /dev/null
+++ b/examples/lang/erc20/.ink/abi_gen/main.rs
@@ -0,0 +1,8 @@
+
+fn main() -> Result<(), std::io::Error> {
+    let abi = contract::ink_generate_abi();
+    let contents = serde_json::to_string_pretty(&abi)?;
+    std::fs::create_dir("target").ok();
+    std::fs::write("target/abi.json", contents)?;
+    Ok(())
+}
diff --git a/examples/lang/erc20/Cargo.toml b/examples/lang/erc20/Cargo.toml
index cea55bc31e..aec0dd9c01 100644
--- a/examples/lang/erc20/Cargo.toml
+++ b/examples/lang/erc20/Cargo.toml
@@ -5,34 +5,47 @@ authors = ["Parity Technologies <admin@parity.io>"]
 edition = "2018"
 
 [dependencies]
+ink_abi = { path = "../../../abi", default-features = false, features = ["derive"], optional = true }
 ink_core = { path = "../../../core", default-features = false }
 ink_model = { path = "../../../model", default-features = false }
 ink_lang = { path = "../../../lang", default-features = false }
+
 scale = { package = "parity-scale-codec", version = "1.0", default-features = false, features = ["derive"] }
+serde = { version = "1.0", default-features = false, features = ["derive"], optional = true }
+type-metadata = { git = "https://github.com/type-metadata/type-metadata.git", default-features = false, features = ["derive"], optional = true }
 
 [lib]
 name = "erc20"
-crate-type = ["cdylib"]
+crate-type = [
+	# Used for normal contract Wasm blobs.
+	"cdylib",
+	# Used for ABI generation.
+	"rlib",
+]
 
 [features]
-default = [
-    "std",
-    "ink_lang/generate-api-description"
-]
+default = ["test-env"]
 std = [
+    "ink_abi/std",
     "ink_core/std",
     "ink_model/std",
     "ink_lang/std",
+    "type-metadata/std",
     "scale/std",
+    "serde/std",
 ]
 test-env = [
-    "std",
+	"std",
     "ink_core/test-env",
     "ink_model/test-env",
     "ink_lang/test-env",
 ]
-generate-api-description = [
-    "ink_lang/generate-api-description"
+ink-generate-abi = [
+    "std",
+    "ink_abi",
+    "serde",
+    "type-metadata",
+    "ink_core/ink-generate-abi",
 ]
 
 [profile.release]
@@ -40,4 +53,11 @@ panic = "abort"
 lto = true
 opt-level = "z"
 overflow-checks = true
-# debug = true
+
+[workspace]
+members = [
+	".ink/abi_gen"
+]
+exclude = [
+	".ink"
+]
diff --git a/examples/lang/erc20/build.sh b/examples/lang/erc20/build.sh
index e8dac30cd1..9c8b8270ea 100755
--- a/examples/lang/erc20/build.sh
+++ b/examples/lang/erc20/build.sh
@@ -7,7 +7,7 @@ PROJNAME=erc20
 # cargo clean
 # rm Cargo.lock
 
-CARGO_INCREMENTAL=0 cargo +nightly build --no-default-features --release --features generate-api-description --target=wasm32-unknown-unknown --verbose
+CARGO_INCREMENTAL=0 cargo +nightly build --no-default-features --release --target=wasm32-unknown-unknown --verbose
 wasm2wat -o target/$PROJNAME.wat target/wasm32-unknown-unknown/release/$PROJNAME.wasm
 cat target/$PROJNAME.wat | sed "s/(import \"env\" \"memory\" (memory (;0;) 2))/(import \"env\" \"memory\" (memory (;0;) 2 16))/" > target/$PROJNAME-fixed.wat
 wat2wasm -o target/$PROJNAME.wasm target/$PROJNAME-fixed.wat
diff --git a/examples/lang/erc20/src/lib.rs b/examples/lang/erc20/src/lib.rs
index 7538bf39eb..3126569b11 100644
--- a/examples/lang/erc20/src/lib.rs
+++ b/examples/lang/erc20/src/lib.rs
@@ -14,7 +14,7 @@
 // You should have received a copy of the GNU General Public License
 // along with ink!.  If not, see <http://www.gnu.org/licenses/>.
 
-#![cfg_attr(not(any(test, feature = "test-env")), no_std)]
+#![cfg_attr(not(feature = "std"), no_std)]
 
 use ink_core::{
     env::DefaultSrmlTypes,
diff --git a/examples/lang/events/.ink/abi_gen/Cargo.toml b/examples/lang/events/.ink/abi_gen/Cargo.toml
new file mode 100644
index 0000000000..6c513fd467
--- /dev/null
+++ b/examples/lang/events/.ink/abi_gen/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "abi-gen"
+version = "0.1.0"
+authors = ["Parity Technologies <admin@parity.io>"]
+edition = "2018"
+publish = false
+
+[[bin]]
+name = "abi-gen"
+path = "main.rs"
+
+[dependencies]
+contract = { path = "../..", package = "events", features = ["ink-generate-abi"] }
+serde = { version = "1.0", features = ["derive"] }
+serde_json = "1.0"
diff --git a/examples/lang/events/.ink/abi_gen/main.rs b/examples/lang/events/.ink/abi_gen/main.rs
new file mode 100644
index 0000000000..576885b191
--- /dev/null
+++ b/examples/lang/events/.ink/abi_gen/main.rs
@@ -0,0 +1,8 @@
+
+fn main() -> Result<(), std::io::Error> {
+    let abi = contract::ink_generate_abi();
+    let contents = serde_json::to_string_pretty(&abi)?;
+    std::fs::create_dir("target").ok();
+    std::fs::write("target/abi.json", contents)?;
+    Ok(())
+}
diff --git a/examples/lang/events/Cargo.toml b/examples/lang/events/Cargo.toml
index 2a3b0f0bec..9a08bafb0f 100644
--- a/examples/lang/events/Cargo.toml
+++ b/examples/lang/events/Cargo.toml
@@ -5,24 +5,49 @@ authors = ["Parity Technologies <admin@parity.io>"]
 edition = "2018"
 
 [dependencies]
-ink_core = { path = "../../../core" }
-ink_model = { path = "../../../model" }
-ink_lang = { path = "../../../lang" }
+ink_abi = { path = "../../../abi", default-features = false, features = ["derive"], optional = true }
+ink_core = { path = "../../../core", default-features = false }
+ink_model = { path = "../../../model", default-features = false }
+ink_lang = { path = "../../../lang", default-features = false }
+
 scale = { package = "parity-scale-codec", version = "1.0", default-features = false, features = ["derive"] }
+serde = { version = "1.0", default-features = false, features = ["derive"], optional = true }
+serde_json = { version = "1.0", default-features = false, optional = true }
+type-metadata = { git = "https://github.com/type-metadata/type-metadata.git", default-features = false, features = ["derive"], optional = true }
 
 [lib]
 name = "events"
-crate-type = ["cdylib"]
+crate-type = [
+	# Used for normal contract Wasm blobs.
+	"cdylib",
+	# Used for ABI generation.
+	"rlib",
+]
 
 [features]
-default = []
+default = ["test-env"]
+std = [
+    "ink_abi/std",
+    "ink_core/std",
+    "ink_model/std",
+    "ink_lang/std",
+    "scale/std",
+    "serde/std",
+    "type-metadata/std",
+]
 test-env = [
+	"std",
     "ink_core/test-env",
     "ink_model/test-env",
     "ink_lang/test-env",
 ]
-generate-api-description = [
-    "ink_lang/generate-api-description"
+ink-generate-abi = [
+    "std",
+    "ink_abi",
+    "serde",
+    "serde_json",
+    "type-metadata",
+    "ink_core/ink-generate-abi",
 ]
 
 [profile.release]
@@ -30,3 +55,11 @@ panic = "abort"
 lto = true
 opt-level = "z"
 overflow-checks = true
+
+[workspace]
+members = [
+	".ink/abi_gen"
+]
+exclude = [
+	".ink"
+]
diff --git a/examples/lang/events/build.sh b/examples/lang/events/build.sh
index fd446bdb2b..036ef7badb 100755
--- a/examples/lang/events/build.sh
+++ b/examples/lang/events/build.sh
@@ -5,7 +5,7 @@ PROJNAME=events
 # cargo clean
 # rm Cargo.lock
 
-CARGO_INCREMENTAL=0 cargo +nightly build --no-default-features --release --features generate-api-description --target=wasm32-unknown-unknown --verbose
+CARGO_INCREMENTAL=0 cargo +nightly build --no-default-features --release --target=wasm32-unknown-unknown --verbose
 wasm2wat -o target/$PROJNAME.wat target/wasm32-unknown-unknown/release/$PROJNAME.wasm
 cat target/$PROJNAME.wat | sed "s/(import \"env\" \"memory\" (memory (;0;) 2))/(import \"env\" \"memory\" (memory (;0;) 2 16))/" > target/$PROJNAME-fixed.wat
 wat2wasm -o target/$PROJNAME.wasm target/$PROJNAME-fixed.wat
diff --git a/examples/lang/events/src/lib.rs b/examples/lang/events/src/lib.rs
index 01943643e1..7c21afb8b7 100644
--- a/examples/lang/events/src/lib.rs
+++ b/examples/lang/events/src/lib.rs
@@ -14,7 +14,7 @@
 // You should have received a copy of the GNU General Public License
 // along with ink!.  If not, see <http://www.gnu.org/licenses/>.
 
-#![cfg_attr(not(any(test, feature = "test-env")), no_std)]
+#![cfg_attr(not(feature = "std"), no_std)]
 
 use ink_core::storage;
 use ink_core::env::{
diff --git a/examples/lang/flipper/.ink/abi_gen/Cargo.toml b/examples/lang/flipper/.ink/abi_gen/Cargo.toml
new file mode 100644
index 0000000000..6d66e0a1fc
--- /dev/null
+++ b/examples/lang/flipper/.ink/abi_gen/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "abi-gen"
+version = "0.1.0"
+authors = ["Parity Technologies <admin@parity.io>"]
+edition = "2018"
+publish = false
+
+[[bin]]
+name = "abi-gen"
+path = "main.rs"
+
+[dependencies]
+contract = { path = "../..", package = "flipper", features = ["ink-generate-abi"] }
+serde = { version = "1.0", features = ["derive"] }
+serde_json = "1.0"
diff --git a/examples/lang/flipper/.ink/abi_gen/main.rs b/examples/lang/flipper/.ink/abi_gen/main.rs
new file mode 100644
index 0000000000..576885b191
--- /dev/null
+++ b/examples/lang/flipper/.ink/abi_gen/main.rs
@@ -0,0 +1,8 @@
+
+fn main() -> Result<(), std::io::Error> {
+    let abi = contract::ink_generate_abi();
+    let contents = serde_json::to_string_pretty(&abi)?;
+    std::fs::create_dir("target").ok();
+    std::fs::write("target/abi.json", contents)?;
+    Ok(())
+}
diff --git a/examples/lang/flipper/Cargo.toml b/examples/lang/flipper/Cargo.toml
index 4d904de1c8..249e372a41 100644
--- a/examples/lang/flipper/Cargo.toml
+++ b/examples/lang/flipper/Cargo.toml
@@ -5,24 +5,49 @@ authors = ["Parity Technologies <admin@parity.io>"]
 edition = "2018"
 
 [dependencies]
-ink_core = { path = "../../../core" }
-ink_model = { path = "../../../model" }
-ink_lang = { path = "../../../lang" }
+ink_abi = { path = "../../../abi", default-features = false, features = ["derive"], optional = true }
+ink_core = { path = "../../../core", default-features = false }
+ink_model = { path = "../../../model", default-features = false }
+ink_lang = { path = "../../../lang", default-features = false }
+
 scale = { package = "parity-scale-codec", version = "1.0", default-features = false, features = ["derive"] }
+serde = { version = "1.0", default-features = false, features = ["derive"], optional = true }
+serde_json = { version = "1.0", default-features = false, optional = true }
+type-metadata = { git = "https://github.com/type-metadata/type-metadata.git", default-features = false, features = ["derive"], optional = true }
 
 [lib]
 name = "flipper"
-crate-type = ["cdylib"]
+crate-type = [
+	# Used for normal contract Wasm blobs.
+	"cdylib",
+	# Used for ABI generation.
+	"rlib",
+]
 
 [features]
-default = []
+default = ["test-env"]
+std = [
+    "ink_abi/std",
+    "ink_core/std",
+    "ink_model/std",
+    "ink_lang/std",
+    "scale/std",
+    "serde/std",
+    "type-metadata/std",
+]
 test-env = [
+    "std",
     "ink_core/test-env",
     "ink_model/test-env",
     "ink_lang/test-env",
 ]
-generate-api-description = [
-    "ink_lang/generate-api-description"
+ink-generate-abi = [
+    "std",
+    "ink_abi",
+    "serde",
+    "serde_json",
+    "type-metadata",
+    "ink_core/ink-generate-abi",
 ]
 
 [profile.release]
@@ -30,3 +55,11 @@ panic = "abort"
 lto = true
 opt-level = "z"
 overflow-checks = true
+
+[workspace]
+members = [
+	".ink/abi_gen"
+]
+exclude = [
+	".ink"
+]
diff --git a/examples/lang/flipper/build.sh b/examples/lang/flipper/build.sh
index 1671df479e..ed3c3e4d26 100755
--- a/examples/lang/flipper/build.sh
+++ b/examples/lang/flipper/build.sh
@@ -7,7 +7,7 @@ PROJNAME=flipper
 # cargo clean
 # rm Cargo.lock
 
-CARGO_INCREMENTAL=0 cargo +nightly build --no-default-features --release --features generate-api-description --target=wasm32-unknown-unknown --verbose
+CARGO_INCREMENTAL=0 cargo +nightly build --no-default-features --release --target=wasm32-unknown-unknown --verbose
 wasm2wat -o target/$PROJNAME.wat target/wasm32-unknown-unknown/release/$PROJNAME.wasm
 cat target/$PROJNAME.wat | sed "s/(import \"env\" \"memory\" (memory (;0;) 2))/(import \"env\" \"memory\" (memory (;0;) 2 16))/" > target/$PROJNAME-fixed.wat
 wat2wasm -o target/$PROJNAME.wasm target/$PROJNAME-fixed.wat
diff --git a/examples/lang/flipper/src/lib.rs b/examples/lang/flipper/src/lib.rs
index 80225aae6a..48da5dc44f 100644
--- a/examples/lang/flipper/src/lib.rs
+++ b/examples/lang/flipper/src/lib.rs
@@ -14,7 +14,7 @@
 // You should have received a copy of the GNU General Public License
 // along with ink!.  If not, see <http://www.gnu.org/licenses/>.
 
-#![cfg_attr(not(any(test, feature = "test-env")), no_std)]
+#![cfg_attr(not(feature = "std"), no_std)]
 
 use ink_core::{
     env::DefaultSrmlTypes,
diff --git a/examples/lang/incrementer/.ink/abi_gen/Cargo.toml b/examples/lang/incrementer/.ink/abi_gen/Cargo.toml
new file mode 100644
index 0000000000..be1383b11b
--- /dev/null
+++ b/examples/lang/incrementer/.ink/abi_gen/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "abi-gen"
+version = "0.1.0"
+authors = ["Parity Technologies <admin@parity.io>"]
+edition = "2018"
+publish = false
+
+[[bin]]
+name = "abi-gen"
+path = "main.rs"
+
+[dependencies]
+contract = { path = "../..", package = "incrementer", features = ["ink-generate-abi"] }
+serde = { version = "1.0", features = ["derive"] }
+serde_json = "1.0"
diff --git a/examples/lang/incrementer/.ink/abi_gen/main.rs b/examples/lang/incrementer/.ink/abi_gen/main.rs
new file mode 100644
index 0000000000..576885b191
--- /dev/null
+++ b/examples/lang/incrementer/.ink/abi_gen/main.rs
@@ -0,0 +1,8 @@
+
+fn main() -> Result<(), std::io::Error> {
+    let abi = contract::ink_generate_abi();
+    let contents = serde_json::to_string_pretty(&abi)?;
+    std::fs::create_dir("target").ok();
+    std::fs::write("target/abi.json", contents)?;
+    Ok(())
+}
diff --git a/examples/lang/incrementer/Cargo.toml b/examples/lang/incrementer/Cargo.toml
index e159d4c5e9..4d528e9552 100644
--- a/examples/lang/incrementer/Cargo.toml
+++ b/examples/lang/incrementer/Cargo.toml
@@ -5,24 +5,45 @@ authors = ["Parity Technologies <admin@parity.io>"]
 edition = "2018"
 
 [dependencies]
-ink_core = { path = "../../../core" }
-ink_model = { path = "../../../model" }
-ink_lang = { path = "../../../lang" }
+ink_abi = { path = "../../../abi", default-features = false, features = ["derive"], optional = true }
+ink_core = { path = "../../../core", default-features = false }
+ink_model = { path = "../../../model", default-features = false }
+ink_lang = { path = "../../../lang", default-features = false }
+
 scale = { package = "parity-scale-codec", version = "1.0", default-features = false, features = ["derive"] }
+type-metadata = { git = "https://github.com/type-metadata/type-metadata.git", default-features = false, features = ["derive"], optional = true }
 
 [lib]
 name = "incrementer"
-crate-type = ["cdylib"]
+crate-type = [
+	# Used for normal contract Wasm blobs.
+	"cdylib",
+	# Used for ABI generation.
+	"rlib",
+]
 
 [features]
-default = []
+default = ["test-env"]
+std = [
+    "ink_abi/std",
+    "ink_core/std",
+    "ink_model/std",
+    "ink_lang/std",
+    "scale/std",
+    "type-metadata/std",
+]
 test-env = [
+	"std",
     "ink_core/test-env",
     "ink_model/test-env",
     "ink_lang/test-env",
+    "type-metadata/std",
 ]
-generate-api-description = [
-    "ink_lang/generate-api-description"
+ink-generate-abi = [
+    "std",
+    "ink_abi",
+    "type-metadata",
+    "ink_core/ink-generate-abi",
 ]
 
 [profile.release]
@@ -30,3 +51,11 @@ panic = "abort"
 lto = true
 opt-level = "z"
 overflow-checks = true
+
+[workspace]
+members = [
+	".ink/abi_gen"
+]
+exclude = [
+	".ink"
+]
diff --git a/examples/lang/incrementer/build.sh b/examples/lang/incrementer/build.sh
index 83d953f0f8..ffc226e76e 100755
--- a/examples/lang/incrementer/build.sh
+++ b/examples/lang/incrementer/build.sh
@@ -3,7 +3,7 @@
 PROJNAME=incrementer
 
 CARGO_INCREMENTAL=0 &&
-cargo +nightly build --release --features generate-api-description --target=wasm32-unknown-unknown --verbose &&
+cargo +nightly build --release --target=wasm32-unknown-unknown --verbose &&
 wasm2wat -o target/$PROJNAME.wat target/wasm32-unknown-unknown/release/$PROJNAME.wasm &&
 cat target/$PROJNAME.wat | sed "s/(import \"env\" \"memory\" (memory (;0;) 2))/(import \"env\" \"memory\" (memory (;0;) 2 16))/" > target/$PROJNAME-fixed.wat &&
 wat2wasm -o target/$PROJNAME.wasm target/$PROJNAME-fixed.wat &&
diff --git a/examples/lang/incrementer/src/lib.rs b/examples/lang/incrementer/src/lib.rs
index bd8d347fbc..eba49e33bf 100644
--- a/examples/lang/incrementer/src/lib.rs
+++ b/examples/lang/incrementer/src/lib.rs
@@ -14,7 +14,7 @@
 // You should have received a copy of the GNU General Public License
 // along with ink!.  If not, see <http://www.gnu.org/licenses/>.
 
-#![cfg_attr(not(any(test, feature = "test-env")), no_std)]
+#![cfg_attr(not(feature = "std"), no_std)]
 
 use ink_core::{
     memory::format,
diff --git a/examples/lang/shared_vec/.ink/abi_gen/Cargo.toml b/examples/lang/shared_vec/.ink/abi_gen/Cargo.toml
new file mode 100644
index 0000000000..5964dfa956
--- /dev/null
+++ b/examples/lang/shared_vec/.ink/abi_gen/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "abi-gen"
+version = "0.1.0"
+authors = ["Parity Technologies <admin@parity.io>"]
+edition = "2018"
+publish = false
+
+[[bin]]
+name = "abi-gen"
+path = "main.rs"
+
+[dependencies]
+contract = { path = "../..", package = "shared_vec", features = ["ink-generate-abi"] }
+serde = { version = "1.0", features = ["derive"] }
+serde_json = "1.0"
diff --git a/examples/lang/shared_vec/.ink/abi_gen/main.rs b/examples/lang/shared_vec/.ink/abi_gen/main.rs
new file mode 100644
index 0000000000..576885b191
--- /dev/null
+++ b/examples/lang/shared_vec/.ink/abi_gen/main.rs
@@ -0,0 +1,8 @@
+
+fn main() -> Result<(), std::io::Error> {
+    let abi = contract::ink_generate_abi();
+    let contents = serde_json::to_string_pretty(&abi)?;
+    std::fs::create_dir("target").ok();
+    std::fs::write("target/abi.json", contents)?;
+    Ok(())
+}
diff --git a/examples/lang/shared_vec/Cargo.toml b/examples/lang/shared_vec/Cargo.toml
index 98d7ed45f2..b456fb2bb2 100644
--- a/examples/lang/shared_vec/Cargo.toml
+++ b/examples/lang/shared_vec/Cargo.toml
@@ -5,24 +5,49 @@ authors = ["Parity Technologies <admin@parity.io>"]
 edition = "2018"
 
 [dependencies]
-ink_core = { path = "../../../core" }
-ink_model = { path = "../../../model" }
-ink_lang = { path = "../../../lang" }
+ink_abi = { path = "../../../abi", default-features = false, features = ["derive"], optional = true }
+ink_core = { path = "../../../core", default-features = false }
+ink_model = { path = "../../../model", default-features = false }
+ink_lang = { path = "../../../lang", default-features = false }
+
 scale = { package = "parity-scale-codec", version = "1.0", default-features = false, features = ["derive"] }
+serde = { version = "1.0", default-features = false, features = ["derive"], optional = true }
+serde_json = { version = "1.0", default-features = false, optional = true }
+type-metadata = { git = "https://github.com/type-metadata/type-metadata.git", default-features = false, features = ["derive"], optional = true }
 
 [lib]
 name = "shared_vec"
-crate-type = ["cdylib"]
+crate-type = [
+	# Used for normal contract Wasm blobs.
+	"cdylib",
+	# Used for ABI generation.
+	"rlib",
+]
 
 [features]
-default = []
+default = ["test-env"]
+std = [
+    "ink_abi/std",
+    "ink_core/std",
+    "ink_model/std",
+    "ink_lang/std",
+    "scale/std",
+    "serde/std",
+    "type-metadata/std",
+]
 test-env = [
+	"std",
     "ink_core/test-env",
     "ink_model/test-env",
     "ink_lang/test-env",
 ]
-generate-api-description = [
-    "ink_lang/generate-api-description"
+ink-generate-abi = [
+    "std",
+    "ink_abi",
+    "serde",
+    "serde_json",
+    "type-metadata",
+    "ink_core/ink-generate-abi",
 ]
 
 [profile.release]
@@ -30,3 +55,11 @@ panic = "abort"
 lto = true
 opt-level = "z"
 overflow-checks = true
+
+[workspace]
+members = [
+	".ink/abi_gen"
+]
+exclude = [
+	".ink"
+]
diff --git a/examples/lang/shared_vec/build.sh b/examples/lang/shared_vec/build.sh
index 1939cc7711..80845184ee 100755
--- a/examples/lang/shared_vec/build.sh
+++ b/examples/lang/shared_vec/build.sh
@@ -3,7 +3,7 @@
 PROJNAME=shared_vec
 
 CARGO_INCREMENTAL=0 &&
-cargo +nightly build --release --features generate-api-description --target=wasm32-unknown-unknown --verbose &&
+cargo +nightly build --release --target=wasm32-unknown-unknown --verbose &&
 wasm2wat -o target/$PROJNAME.wat target/wasm32-unknown-unknown/release/$PROJNAME.wasm &&
 cat target/$PROJNAME.wat | sed "s/(import \"env\" \"memory\" (memory (;0;) 2))/(import \"env\" \"memory\" (memory (;0;) 2 16))/" > target/$PROJNAME-fixed.wat &&
 wat2wasm -o target/$PROJNAME.wasm target/$PROJNAME-fixed.wat &&
diff --git a/examples/lang/shared_vec/src/lib.rs b/examples/lang/shared_vec/src/lib.rs
index 1649c639cf..937d4b6c45 100644
--- a/examples/lang/shared_vec/src/lib.rs
+++ b/examples/lang/shared_vec/src/lib.rs
@@ -14,19 +14,27 @@
 // You should have received a copy of the GNU General Public License
 // along with ink!.  If not, see <http://www.gnu.org/licenses/>.
 
-#![cfg_attr(not(any(test, feature = "test-env")), no_std)]
+#![cfg_attr(not(feature = "std"), no_std)]
 
-use ink_core::storage;
+use ink_core::{storage, storage::Flush};
 use ink_lang::contract;
 use scale::{Encode, Decode};
 
+#[cfg(feature = "ink-generate-abi")]
+use type_metadata::Metadata;
+
 /// Access rights to the shared vector.
 #[derive(Encode, Decode)]
+#[cfg_attr(feature = "ink-generate-abi", derive(Metadata))]
 pub struct Access {
     pub begin: Option<u32>,
     pub end: Option<u32>,
 }
 
+impl Flush for Access {
+	fn flush(&mut self) {}
+}
+
 impl Access {
     /// Creates unlimited access rights.
     pub fn unlimited() -> Self {
diff --git a/lang/Cargo.toml b/lang/Cargo.toml
index c79eabc6e8..6921449037 100644
--- a/lang/Cargo.toml
+++ b/lang/Cargo.toml
@@ -50,5 +50,4 @@ std = [
 ]
 test-env = [
     "ink_model/test-env",
-]
-generate-api-description = []
+]
\ No newline at end of file
diff --git a/lang/src/api.rs b/lang/src/api.rs
deleted file mode 100644
index df0d6bf430..0000000000
--- a/lang/src/api.rs
+++ /dev/null
@@ -1,797 +0,0 @@
-// Copyright 2018-2019 Parity Technologies (UK) Ltd.
-// This file is part of ink!.
-//
-// ink! is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// ink! is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with ink!.  If not, see <http://www.gnu.org/licenses/>.
-
-use crate::{
-    ast,
-    hir,
-};
-use serde::{
-    Deserialize,
-    Serialize,
-};
-use std::convert::TryFrom;
-use syn::{
-    self,
-    Result,
-};
-
-/// Describes a message parameter or return type.
-#[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
-#[serde(untagged)]
-pub enum TypeDescription {
-    /// The `bool` primitive type.
-    Primitive(PrimitiveTypeDescription),
-    /// The tuple type
-    Tuple(TupleTypeDescription),
-    /// The fixed size array type
-    Array(ArrayTypeDescription),
-    /// A concrete `Option` type.
-    Option(OptionTypeDescription),
-    /// A concrete `Result` type.
-    Result(ResultTypeDescription),
-    /// A concrete `Vec` type.
-    Vec(VecTypeDescription),
-}
-
-/// Describes an option param or return type.
-#[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
-pub enum OptionTypeDescription {
-    #[serde(rename = "Option<T>")]
-    Single {
-        /// The generic type param.
-        #[serde(rename = "T")]
-        inner: Box<TypeDescription>,
-    },
-}
-
-impl TryFrom<&syn::TypePath> for OptionTypeDescription {
-    type Error = syn::Error;
-
-    fn try_from(type_path: &syn::TypePath) -> Result<Self> {
-        if type_path.qself.is_some() || type_path.path.leading_colon.is_some() {
-            bail!(type_path, "`Option` cannot be qualified or start with `::`")
-        }
-        if type_path.path.segments.len() != 1 {
-            bail!(type_path, "too many path segments for an `Option` type")
-        }
-        let seg = &type_path.path.segments[0];
-        if seg.ident != "Option" {
-            bail!(type_path, "invalid ident for `Option` type")
-        }
-        match &seg.arguments {
-            syn::PathArguments::AngleBracketed(generic_args) => {
-                if generic_args.args.len() != 1 {
-                    bail!(generic_args, "too many generic args for `Option` type")
-                }
-                match &generic_args.args[0] {
-                    syn::GenericArgument::Type(ty) => {
-                        Ok(OptionTypeDescription::Single {
-                            inner: Box::new(TypeDescription::try_from(ty)?),
-                        })
-                    }
-                    invalid => bail!(invalid, "invalid generic type args for `Option`"),
-                }
-            }
-            invalid => bail!(invalid, "invalid type arguments for `Option`"),
-        }
-    }
-}
-
-/// Describes a `Vec` param or return type.
-///
-/// # Note
-///
-/// With `Vec` we refer to `memory::Vec` here.
-#[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
-pub enum VecTypeDescription {
-    #[serde(rename = "Vec<T>")]
-    Single {
-        /// The generic type param.
-        #[serde(rename = "T")]
-        elem_type: Box<TypeDescription>,
-    },
-}
-
-impl TryFrom<&syn::TypePath> for VecTypeDescription {
-    type Error = syn::Error;
-
-    fn try_from(type_path: &syn::TypePath) -> Result<Self> {
-        if type_path.qself.is_some() || type_path.path.leading_colon.is_some() {
-            bail!(type_path, "`Vec` cannot be qualified or start with `::`")
-        }
-        if type_path.path.segments.len() != 1 {
-            bail!(type_path, "too many path segments for an `Vec` type")
-        }
-        let seg = &type_path.path.segments[0];
-        if seg.ident != "Vec" {
-            bail!(type_path, "invalid ident for `Vec` type")
-        }
-        match &seg.arguments {
-            syn::PathArguments::AngleBracketed(generic_args) => {
-                if generic_args.args.len() != 1 {
-                    bail!(generic_args, "too many generic args for `Vec` type")
-                }
-                match &generic_args.args[0] {
-                    syn::GenericArgument::Type(ty) => {
-                        Ok(VecTypeDescription::Single {
-                            elem_type: Box::new(TypeDescription::try_from(ty)?),
-                        })
-                    }
-                    invalid => bail!(invalid, "invalid generic type args for `Vec`"),
-                }
-            }
-            invalid => bail!(invalid, "invalid type arguments for `Vec`"),
-        }
-    }
-}
-
-/// Describes a result param or return type.
-#[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
-pub enum ResultTypeDescription {
-    #[serde(rename = "Result<T,E>")]
-    Single {
-        /// The `Ok`-type.
-        #[serde(rename = "T")]
-        ok_type: Box<TypeDescription>,
-        /// The `Err`-type.
-        #[serde(rename = "E")]
-        err_type: Box<TypeDescription>,
-    },
-}
-
-impl TryFrom<&syn::TypePath> for ResultTypeDescription {
-    type Error = syn::Error;
-
-    fn try_from(type_path: &syn::TypePath) -> Result<Self> {
-        if type_path.qself.is_some() || type_path.path.leading_colon.is_some() {
-            bail!(type_path, "`Result` cannot be qualified or start with `::`")
-        }
-        if type_path.path.segments.len() != 1 {
-            bail!(type_path, "too many path segments for an `Result` type")
-        }
-        let seg = &type_path.path.segments[0];
-        if seg.ident != "Result" {
-            bail!(type_path, "invalid ident for `Result` type")
-        }
-        match &seg.arguments {
-            syn::PathArguments::AngleBracketed(generic_args) => {
-                if generic_args.args.len() != 2 {
-                    bail!(
-                        generic_args,
-                        "`Result` type requires 2 generic type arguments"
-                    )
-                }
-                let ok_type = match &generic_args.args[0] {
-                    syn::GenericArgument::Type(ty) => TypeDescription::try_from(ty),
-                    invalid => bail!(invalid, "invalid generic type args for `Result`"),
-                }?;
-                let err_type = match &generic_args.args[1] {
-                    syn::GenericArgument::Type(ty) => TypeDescription::try_from(ty),
-                    invalid => bail!(invalid, "invalid generic type args for `Result`"),
-                }?;
-                Ok(ResultTypeDescription::Single {
-                    ok_type: Box::new(ok_type),
-                    err_type: Box::new(err_type),
-                })
-            }
-            invalid => bail!(invalid, "invalid type arguments for `Result`"),
-        }
-    }
-}
-
-impl TryFrom<&syn::Type> for TypeDescription {
-    type Error = syn::Error;
-
-    fn try_from(ty: &syn::Type) -> Result<Self> {
-        match ty {
-            syn::Type::Tuple(tuple) => {
-                TupleTypeDescription::try_from(tuple).map(TypeDescription::Tuple)
-            }
-            syn::Type::Array(array) => {
-                ArrayTypeDescription::try_from(array).map(TypeDescription::Array)
-            }
-            syn::Type::Path(path) => {
-                if path.path.segments.len() != 1 || path.path.leading_colon.is_some() {
-                    bail!(path, "invalid self qualifier or leading `::` for type")
-                }
-                let ident = &path.path.segments[0].ident;
-                match ident.to_string().as_str() {
-                    "Option" => {
-                        OptionTypeDescription::try_from(path).map(TypeDescription::Option)
-                    }
-                    "Result" => {
-                        ResultTypeDescription::try_from(path).map(TypeDescription::Result)
-                    }
-                    "Vec" => VecTypeDescription::try_from(path).map(TypeDescription::Vec),
-                    _ => {
-                        PrimitiveTypeDescription::try_from(path)
-                            .map(TypeDescription::Primitive)
-                    }
-                }
-            }
-            invalid => bail!(invalid, "invalid or unsupported type",),
-        }
-    }
-}
-
-#[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
-pub enum PrimitiveTypeDescription {
-    /// The `bool` primitive type.
-    #[serde(rename = "bool")]
-    Bool,
-    /// The `u8` primitive unsigned integer.
-    #[serde(rename = "u8")]
-    U8,
-    /// The `u16` primitive unsigned integer.
-    #[serde(rename = "u16")]
-    U16,
-    /// The `u32` primitive unsigned integer.
-    #[serde(rename = "u32")]
-    U32,
-    /// The `u64` primitive unsigned integer.
-    #[serde(rename = "u64")]
-    U64,
-    /// The `u128` primitive unsigned integer.
-    #[serde(rename = "u128")]
-    U128,
-    /// The `i8` primitive signed integer.
-    #[serde(rename = "i8")]
-    I8,
-    /// The `i16` primitive signed integer.
-    #[serde(rename = "i16")]
-    I16,
-    /// The `i32` primitive signed integer.
-    #[serde(rename = "i32")]
-    I32,
-    /// The `i64` primitive signed integer.
-    #[serde(rename = "i64")]
-    I64,
-    /// The `i128` primitive signed integer.
-    #[serde(rename = "i128")]
-    I128,
-    /// The SRML address type.
-    AccountId,
-    /// The SRML balance type.
-    Balance,
-    /// The SRML hash type.
-    Hash,
-    /// The SRML moment type.
-    Moment,
-    /// The SRML block number type.
-    BlockNumber,
-}
-
-impl TryFrom<&syn::TypePath> for PrimitiveTypeDescription {
-    type Error = syn::Error;
-
-    fn try_from(ty: &syn::TypePath) -> Result<Self> {
-        use quote::ToTokens;
-
-        match ty.into_token_stream().to_string().as_str() {
-            "bool" => Ok(PrimitiveTypeDescription::Bool),
-            "u8" => Ok(PrimitiveTypeDescription::U8),
-            "u16" => Ok(PrimitiveTypeDescription::U16),
-            "u32" => Ok(PrimitiveTypeDescription::U32),
-            "u64" => Ok(PrimitiveTypeDescription::U64),
-            "u128" => Ok(PrimitiveTypeDescription::U128),
-            "i8" => Ok(PrimitiveTypeDescription::I8),
-            "i16" => Ok(PrimitiveTypeDescription::I16),
-            "i32" => Ok(PrimitiveTypeDescription::I32),
-            "i64" => Ok(PrimitiveTypeDescription::I64),
-            "i128" => Ok(PrimitiveTypeDescription::I128),
-            "AccountId" => Ok(PrimitiveTypeDescription::AccountId),
-            "Balance" => Ok(PrimitiveTypeDescription::Balance),
-            "Hash" => Ok(PrimitiveTypeDescription::Hash),
-            "Moment" => Ok(PrimitiveTypeDescription::Moment),
-            "BlockNumber" => Ok(PrimitiveTypeDescription::BlockNumber),
-            unsupported => {
-                bail!(
-                    ty,
-                    "{} is unsupported as message interface type",
-                    unsupported
-                )
-            }
-        }
-    }
-}
-
-/// Describes a tuple type
-#[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
-#[serde(transparent)]
-pub struct TupleTypeDescription {
-    elems: Vec<TypeDescription>,
-}
-
-impl TryFrom<&syn::TypeTuple> for TupleTypeDescription {
-    type Error = syn::Error;
-
-    fn try_from(arg: &syn::TypeTuple) -> Result<Self> {
-        let elems = arg
-            .elems
-            .iter()
-            .map(TypeDescription::try_from)
-            .collect::<Result<_>>()?;
-        Ok(TupleTypeDescription { elems })
-    }
-}
-
-/// Describes a fixed size array type
-#[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
-pub enum ArrayTypeDescription {
-    #[serde(rename = "[T;n]")]
-    FixedLength {
-        #[serde(rename = "T")]
-        inner: Box<TypeDescription>,
-        #[serde(rename = "n")]
-        arity: u32,
-    },
-}
-
-impl TryFrom<&syn::TypeArray> for ArrayTypeDescription {
-    type Error = syn::Error;
-
-    fn try_from(arg: &syn::TypeArray) -> Result<Self> {
-        let ty = TypeDescription::try_from(&*arg.elem)?;
-        if let syn::Expr::Lit(syn::ExprLit {
-            lit: syn::Lit::Int(ref int_lit),
-            ..
-        }) = arg.len
-        {
-            Ok(ArrayTypeDescription::FixedLength {
-                inner: Box::new(ty),
-                arity: int_lit.value() as u32,
-            })
-        } else {
-            bail!(arg.len, "invalid array length expression")
-        }
-    }
-}
-
-/// Describes a pair of parameter name and type.
-#[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
-pub struct ParamDescription {
-    /// The name of the parameter.
-    name: String,
-    /// The type of the parameter.
-    #[serde(rename = "type")]
-    ty: TypeDescription,
-}
-
-impl TryFrom<&syn::ArgCaptured> for ParamDescription {
-    type Error = syn::Error;
-
-    fn try_from(arg: &syn::ArgCaptured) -> Result<Self> {
-        let name = match &arg.pat {
-            syn::Pat::Ident(ident) => ident.ident.to_string(),
-            _ => {
-                bail!(arg.pat, "unsupported type pattern, currently only identifiers like `foo` are supported")
-            }
-        };
-        Ok(Self {
-            name,
-            ty: TypeDescription::try_from(&arg.ty)?,
-        })
-    }
-}
-
-/// Describes the deploy handler of a contract.
-#[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
-pub struct DeployDescription {
-    /// The parameters of the deploy handler.
-    args: Vec<ParamDescription>,
-}
-
-impl TryFrom<&hir::DeployHandler> for DeployDescription {
-    type Error = syn::Error;
-
-    fn try_from(deploy_handler: &hir::DeployHandler) -> Result<Self> {
-        let args = deploy_handler
-            .decl
-            .inputs
-            .iter()
-            .filter_map(|arg| {
-                match arg {
-                    ast::FnArg::Captured(captured) => {
-                        let description = ParamDescription::try_from(captured);
-                        Some(description)
-                    }
-                    _ => None,
-                }
-            })
-            .collect::<Result<Vec<_>>>()?;
-        Ok(Self { args })
-    }
-}
-
-/// Describes the return type of a contract message.
-#[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
-#[serde(transparent)]
-pub struct ReturnTypeDescription {
-    #[serde(rename = "type")]
-    opt_type: Option<TypeDescription>,
-}
-
-impl ReturnTypeDescription {
-    /// Creates a new return type description from the given optional type.
-    pub fn new<T>(opt_type: T) -> Self
-    where
-        T: Into<Option<TypeDescription>>,
-    {
-        Self {
-            opt_type: opt_type.into(),
-        }
-    }
-}
-
-impl TryFrom<&syn::ReturnType> for ReturnTypeDescription {
-    type Error = syn::Error;
-
-    fn try_from(ret_ty: &syn::ReturnType) -> Result<Self> {
-        match ret_ty {
-            syn::ReturnType::Default => Ok(ReturnTypeDescription::new(None)),
-            syn::ReturnType::Type(_, ty) => {
-                Ok(ReturnTypeDescription::new(Some(TypeDescription::try_from(
-                    &**ty,
-                )?)))
-            }
-        }
-    }
-}
-
-/// Describes a contract message.
-#[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
-pub struct MessageDescription {
-    /// The name of the message.
-    name: String,
-    /// The selector hash of the message.
-    selector: u64,
-    /// If the message is allowed to mutate the contract state.
-    mutates: bool,
-    /// The parameters of the message.
-    args: Vec<ParamDescription>,
-    /// The return type of the message.
-    return_type: ReturnTypeDescription,
-}
-
-impl TryFrom<&hir::Message> for MessageDescription {
-    type Error = syn::Error;
-
-    fn try_from(message: &hir::Message) -> Result<Self> {
-        Ok(Self {
-            name: message.sig.ident.to_string(),
-            selector: message.selector().into(),
-            mutates: message.is_mut(),
-            args: {
-                message
-                    .sig
-                    .decl
-                    .inputs
-                    .iter()
-                    .filter_map(|arg| {
-                        match arg {
-                            ast::FnArg::Captured(captured) => {
-                                Some(ParamDescription::try_from(captured))
-                            }
-                            _ => None,
-                        }
-                    })
-                    .collect::<Result<Vec<_>>>()?
-            },
-            return_type: ReturnTypeDescription::try_from(&message.sig.decl.output)?,
-        })
-    }
-}
-
-/// Describes a contract.
-#[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
-pub struct ContractDescription {
-    /// The name of the contract.
-    name: String,
-    /// The deploy handler of the contract.
-    deploy: DeployDescription,
-    /// The external messages of the contract.
-    messages: Vec<MessageDescription>,
-}
-
-impl ContractDescription {
-    /// Returns the name of the contract.
-    pub fn name(&self) -> &str {
-        &self.name
-    }
-}
-
-impl TryFrom<&hir::Contract> for ContractDescription {
-    type Error = syn::Error;
-
-    fn try_from(contract: &hir::Contract) -> Result<Self> {
-        Ok(ContractDescription {
-            name: contract.name.to_string(),
-            deploy: DeployDescription::try_from(&contract.on_deploy)?,
-            messages: {
-                contract
-                    .messages
-                    .iter()
-                    .map(MessageDescription::try_from)
-                    .collect::<Result<Vec<_>>>()?
-            },
-        })
-    }
-}
-
-/// Writes a JSON API description into the `target/` folder.
-pub fn generate_api_description(contract: &hir::Contract) -> Result<()> {
-    let description = ContractDescription::try_from(contract)?;
-    let contents = serde_json::to_string(&description)
-        .expect("Failed at generating JSON API description as JSON");
-    let mut path_buf = String::from("target/");
-    path_buf.push_str(description.name());
-    path_buf.push_str(".json");
-    std::fs::create_dir("target").unwrap_or(());
-    std::fs::write(path_buf, contents)
-        .expect("Failed at writing JSON API descrition to file");
-    Ok(())
-}
-
-#[cfg(test)]
-mod tests {
-    use super::{
-        PrimitiveTypeDescription::*,
-        TypeDescription::*,
-        *,
-    };
-    use syn::parse_quote;
-
-    fn assert_eq_type_description(ty: syn::Type, expected: TypeDescription) {
-        let actual = TypeDescription::try_from(&ty).unwrap();
-        assert_eq!(expected, actual);
-    }
-
-    fn assert_json_roundtrip(ty: syn::Type, json: &str) {
-        let td = TypeDescription::try_from(&ty).unwrap();
-        let actual_json = serde_json::to_string(&td).unwrap();
-        assert_eq!(json, actual_json);
-        let deserialized: TypeDescription = serde_json::de::from_str(json).unwrap();
-        assert_eq!(td, deserialized);
-    }
-
-    #[test]
-    fn primitives() {
-        assert_eq_type_description(
-            parse_quote!(u16),
-            Primitive(PrimitiveTypeDescription::U16),
-        );
-        assert_eq_type_description(
-            parse_quote!(bool),
-            Primitive(PrimitiveTypeDescription::Bool),
-        );
-        assert_eq_type_description(
-            parse_quote!(i64),
-            Primitive(PrimitiveTypeDescription::I64),
-        );
-        assert_eq_type_description(
-            parse_quote!(AccountId),
-            Primitive(PrimitiveTypeDescription::AccountId),
-        );
-        assert_eq_type_description(
-            parse_quote!(Moment),
-            Primitive(PrimitiveTypeDescription::Moment),
-        );
-        assert_eq_type_description(
-            parse_quote!(BlockNumber),
-            Primitive(PrimitiveTypeDescription::BlockNumber),
-        );
-    }
-
-    #[test]
-    fn tuple_basic() {
-        assert_eq_type_description(
-            parse_quote!((bool, i32)),
-            Tuple(TupleTypeDescription {
-                elems: vec![Primitive(Bool), Primitive(I32)],
-            }),
-        )
-    }
-
-    #[test]
-    fn tuple_nested() {
-        assert_eq_type_description(
-            parse_quote!((u32, (bool, i32))),
-            Tuple(TupleTypeDescription {
-                elems: vec![
-                    Primitive(U32),
-                    Tuple(TupleTypeDescription {
-                        elems: vec![Primitive(Bool), Primitive(I32)],
-                    }),
-                ],
-            }),
-        )
-    }
-
-    #[test]
-    fn tuple_of_arrays() {
-        assert_eq_type_description(
-            parse_quote!(([i32; 2], [u32; 2])),
-            Tuple(TupleTypeDescription {
-                elems: vec![
-                    Array(ArrayTypeDescription::FixedLength {
-                        inner: Box::new(Primitive(I32)),
-                        arity: 2,
-                    }),
-                    Array(ArrayTypeDescription::FixedLength {
-                        inner: Box::new(Primitive(U32)),
-                        arity: 2,
-                    }),
-                ],
-            }),
-        )
-    }
-
-    #[test]
-    fn array_basic() {
-        assert_eq_type_description(
-            parse_quote!([u32; 5]),
-            Array(ArrayTypeDescription::FixedLength {
-                inner: Box::new(Primitive(U32)),
-                arity: 5,
-            }),
-        )
-    }
-
-    #[test]
-    fn array_nested() {
-        assert_eq_type_description(
-            parse_quote!([[u32; 5]; 3]),
-            Array(ArrayTypeDescription::FixedLength {
-                inner: Box::new(Array(ArrayTypeDescription::FixedLength {
-                    inner: Box::new(Primitive(U32)),
-                    arity: 5,
-                })),
-                arity: 3,
-            }),
-        )
-    }
-
-    #[test]
-    fn array_of_tuples() {
-        assert_eq_type_description(
-            parse_quote!([(bool, u32); 5]),
-            Array(ArrayTypeDescription::FixedLength {
-                inner: Box::new(Tuple(TupleTypeDescription {
-                    elems: vec![Primitive(Bool), Primitive(U32)],
-                })),
-                arity: 5,
-            }),
-        )
-    }
-
-    #[test]
-    fn tuple_json() {
-        assert_json_roundtrip(parse_quote!((u64, i32)), r#"["u64","i32"]"#)
-    }
-
-    #[test]
-    fn array_json() {
-        assert_json_roundtrip(parse_quote!([u32; 5]), r#"{"[T;n]":{"T":"u32","n":5}}"#)
-    }
-
-    fn expect_failure(input: syn::Type, expected_err: &str) {
-        let res = TypeDescription::try_from(&input).map_err(|err| format!("{}", err));
-        assert_eq!(res, Err(String::from(expected_err)));
-    }
-
-    #[test]
-    fn option_json_failure() {
-        expect_failure(
-            parse_quote!(<Self as Foo>::Option<i32>),
-            "invalid self qualifier or leading `::` for type",
-        );
-        expect_failure(
-            parse_quote!(::Option<i32>),
-            "invalid self qualifier or leading `::` for type",
-        );
-        expect_failure(
-            parse_quote!(Option<bool, i32>),
-            "too many generic args for `Option` type",
-        );
-        expect_failure(
-            parse_quote!(Option<'a>),
-            "invalid generic type args for `Option`",
-        );
-    }
-
-    #[test]
-    fn option_json_success() {
-        assert_json_roundtrip(parse_quote!(Option<i32>), r#"{"Option<T>":{"T":"i32"}}"#);
-        assert_json_roundtrip(
-            parse_quote!(Option<(bool, i32)>),
-            r#"{"Option<T>":{"T":["bool","i32"]}}"#,
-        );
-        assert_json_roundtrip(
-            parse_quote!(Option<Option<i32>>),
-            r#"{"Option<T>":{"T":{"Option<T>":{"T":"i32"}}}}"#,
-        );
-    }
-
-    #[test]
-    fn vec_json_failure() {
-        expect_failure(
-            parse_quote!(<Self as Foo>::Vec<i32>),
-            "invalid self qualifier or leading `::` for type",
-        );
-        expect_failure(
-            parse_quote!(::Vec<i32>),
-            "invalid self qualifier or leading `::` for type",
-        );
-        expect_failure(
-            parse_quote!(Vec<bool, i32>),
-            "too many generic args for `Vec` type",
-        );
-        expect_failure(parse_quote!(Vec<'a>), "invalid generic type args for `Vec`");
-    }
-
-    #[test]
-    fn vec_json_success() {
-        assert_json_roundtrip(parse_quote!(Vec<i32>), r#"{"Vec<T>":{"T":"i32"}}"#);
-        assert_json_roundtrip(
-            parse_quote!(Vec<(bool, i32)>),
-            r#"{"Vec<T>":{"T":["bool","i32"]}}"#,
-        );
-        assert_json_roundtrip(
-            parse_quote!(Vec<Vec<i32>>),
-            r#"{"Vec<T>":{"T":{"Vec<T>":{"T":"i32"}}}}"#,
-        );
-    }
-
-    #[test]
-    fn result_json_failure() {
-        expect_failure(
-            parse_quote!(<Self as Foo>::Result<bool, i32>),
-            "invalid self qualifier or leading `::` for type",
-        );
-        expect_failure(
-            parse_quote!(::Result<bool, i32>),
-            "invalid self qualifier or leading `::` for type",
-        );
-        expect_failure(
-            parse_quote!(Result<u32>),
-            "`Result` type requires 2 generic type arguments",
-        );
-        expect_failure(
-            parse_quote!(Result<u16, u32, u64>),
-            "`Result` type requires 2 generic type arguments",
-        );
-        expect_failure(
-            parse_quote!(Result<'a, bool>),
-            "invalid generic type args for `Result`",
-        );
-    }
-
-    #[test]
-    fn result_json_success() {
-        assert_json_roundtrip(
-            parse_quote!(Result<bool, i32>),
-            r#"{"Result<T,E>":{"T":"bool","E":"i32"}}"#,
-        );
-        assert_json_roundtrip(
-            parse_quote!(Result<(bool, i32), [u8; 8]>),
-            r#"{"Result<T,E>":{"T":["bool","i32"],"E":{"[T;n]":{"T":"u8","n":8}}}}"#,
-        );
-        assert_json_roundtrip(
-            parse_quote!(Result<Result<u8,i8>,Result<u16,i16>>),
-            r#"{"Result<T,E>":{"T":{"Result<T,E>":{"T":"u8","E":"i8"}},"E":{"Result<T,E>":{"T":"u16","E":"i16"}}}}"#,
-        );
-    }
-}
diff --git a/lang/src/ast.rs b/lang/src/ast.rs
index 6a624b0e7d..14c72632c0 100644
--- a/lang/src/ast.rs
+++ b/lang/src/ast.rs
@@ -124,6 +124,20 @@ pub struct EventArg {
     pub ty: syn::Type,
 }
 
+impl EventArg {
+    /// Returns `true` if the event argument is indexed.
+    pub fn is_indexed(&self) -> bool {
+        self.attrs
+            .iter()
+            .find(|attr| {
+                attr.style == syn::AttrStyle::Outer
+                    && attr.path.is_ident("indexed")
+                    && attr.tts.is_empty()
+            })
+            .is_some()
+    }
+}
+
 #[derive(Debug)]
 pub struct ItemState {
     pub attrs: Vec<syn::Attribute>,
@@ -313,6 +327,16 @@ pub enum FnArg {
     Captured(syn::ArgCaptured),
 }
 
+impl FnArg {
+    /// Returns `true` if the fn argument is captured.
+    pub fn is_captured(&self) -> Option<&syn::ArgCaptured> {
+        match self {
+            FnArg::Captured(capt) => Some(capt),
+            _ => None,
+        }
+    }
+}
+
 impl quote::ToTokens for FnArg {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
         match self {
diff --git a/lang/src/contract.rs b/lang/src/contract.rs
index 0509ba2338..21280ec1a5 100644
--- a/lang/src/contract.rs
+++ b/lang/src/contract.rs
@@ -33,17 +33,6 @@ pub fn generate(input: TokenStream2) -> TokenStream2 {
 pub fn generate_or_err(input: TokenStream2) -> Result<TokenStream2> {
     let ast_contract = parser::parse_contract(input.clone())?;
     let hir_contract = hir::Contract::from_ast(&ast_contract)?;
-    generate_api_description(&hir_contract)?;
     let tokens = gen::generate_code(&hir_contract);
     Ok(tokens)
 }
-
-#[cfg(feature = "generate-api-description")]
-fn generate_api_description(contract: &hir::Contract) -> Result<()> {
-    crate::api::generate_api_description(&contract)
-}
-
-#[cfg(not(feature = "generate-api-description"))]
-fn generate_api_description(_contract: &hir::Contract) -> Result<()> {
-    Ok(())
-}
diff --git a/lang/src/gen/abi.rs b/lang/src/gen/abi.rs
new file mode 100644
index 0000000000..8b3fcdf53f
--- /dev/null
+++ b/lang/src/gen/abi.rs
@@ -0,0 +1,273 @@
+// Copyright 2018-2019 Parity Technologies (UK) Ltd.
+// This file is part of ink!.
+//
+// ink! is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// ink! is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with ink!.  If not, see <http://www.gnu.org/licenses/>.
+
+//! Code generation for smart contract ABI and metadata generation.
+//!
+//! This two-steps process is required because Rust macros (and thus `ink_lang`)
+//! are not able to access type information or anything that is related to that.
+
+use crate::{
+    ast,
+    hir,
+};
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+use syn::{
+    self,
+    punctuated::Punctuated,
+    Token,
+};
+
+/// Trims a doc string obtained from an attribute token stream into the actual doc string.
+///
+/// Practically speaking this method removes the trailing start `" = \""` and end `\"`
+/// of documentation strings coming from Syn attribute token streams.
+fn trim_doc_string(attr: &syn::Attribute) -> String {
+    attr.tts
+        .to_string()
+        .trim_start_matches('=')
+        .trim_start()
+        .trim_start_matches("r")
+        .trim_start_matches("\"")
+        .trim_end_matches("\"")
+        .trim()
+        .into()
+}
+
+pub fn generate_code(tokens: &mut TokenStream2, contract: &hir::Contract) {
+    let abi_mod_body = generate_abi_mod_body(contract);
+    tokens.extend(abi_mod_body);
+}
+
+fn generate_abi_mod_body(contract: &hir::Contract) -> TokenStream2 {
+    let ink_generate_abi_contract = generate_abi_contract(contract);
+    let ink_generate_abi_layout = generate_abi_layout(contract);
+
+    quote! {
+        #[cfg(feature = "ink-generate-abi")]
+        pub fn ink_generate_abi() -> ink_abi::InkProject {
+            let contract = {
+                #ink_generate_abi_contract
+            };
+            let layout = {
+                #ink_generate_abi_layout
+            };
+            ink_abi::InkProject::new(layout, contract)
+        }
+    }
+}
+
+fn generate_abi_deploy_handler(contract: &hir::Contract) -> TokenStream2 {
+    let deploy_handler = &contract.on_deploy;
+
+    let args = deploy_handler
+        .decl
+        .inputs
+        .iter()
+        .filter_map(ast::FnArg::is_captured)
+        .map(|capt| {
+            let name = match &capt.pat {
+                syn::Pat::Ident(pat_ident) => {
+                    if pat_ident.by_ref.is_none()
+                        && pat_ident.mutability.is_none()
+                        && pat_ident.subpat.is_none()
+                    {
+                        pat_ident.ident.to_string()
+                    } else {
+                        unreachable!("encountered invalid deploy argument")
+                    }
+                }
+                syn::Pat::Path(pat_path) => {
+                    if pat_path.qself.is_none()
+                        && pat_path.path.leading_colon.is_none()
+                        && pat_path.path.segments.len() == 1
+                        && pat_path.path.segments[0].arguments.is_empty()
+                    {
+                        pat_path.path.segments[0].ident.to_string()
+                    } else {
+                        unreachable!("invalid arg name encountered")
+                    }
+                }
+                _ => {
+                    unreachable!(
+                        "encountered invalid argument syntax: the only allowed is `ident : type`",
+                    )
+                }
+            };
+            let ty = &capt.ty;
+            quote! {
+                ink_abi::MessageParamSpec::new::<#ty>(#name)
+            }
+        })
+        .collect::<Punctuated<_, Token![,]>>();
+    let docs = deploy_handler.docs().map(trim_doc_string);
+
+    quote! {
+        ink_abi::DeploySpec::new()
+            .args(vec![
+                #(#args)*
+            ])
+            .docs(vec![
+                #(#docs)*
+            ])
+            .done()
+    }
+}
+
+fn generate_abi_messages<'a>(
+    contract: &'a hir::Contract,
+) -> impl Iterator<Item = TokenStream2> + 'a {
+    contract.messages.iter().map(|message| {
+        let selector = message.selector();
+        let is_mut = message.is_mut();
+        let docs = message.docs().map(trim_doc_string);
+        let name = message.sig.ident.to_string();
+        let inputs = message
+            .sig
+            .decl
+            .inputs
+            .iter()
+            .filter_map(ast::FnArg::is_captured)
+            .map(|capt| {
+                let name: String = match &capt.pat {
+                    syn::Pat::Ident(pat_ident) => {
+                        if pat_ident.by_ref.is_none()
+                            && pat_ident.mutability.is_none()
+                            && pat_ident.subpat.is_none()
+                        {
+                            pat_ident.ident.to_string()
+                        } else {
+                            unreachable!("encountered invalid deploy argument")
+                        }
+                    }
+                    syn::Pat::Path(pat_path) => {
+                        if pat_path.qself.is_none()
+                            && pat_path.path.leading_colon.is_none()
+                            && pat_path.path.segments.len() == 1
+                            && pat_path.path.segments[0].arguments.is_empty()
+                        {
+                            pat_path.path.segments[0].ident.to_string()
+                        } else {
+                            unreachable!("invalid arg name encountered")
+                        }
+                    }
+                    _ => unreachable!("encountered invalid argument syntax: the only allowed is `ident : type`"),
+                };
+                let ty = &capt.ty;
+                quote! {
+                    ink_abi::MessageParamSpec::new::<#ty>(#name)
+                }
+            });
+        let ret_type = match &message.sig.decl.output {
+            syn::ReturnType::Default => {
+                quote! {
+                    ink_abi::ReturnTypeSpec::none()
+                }
+            }
+            syn::ReturnType::Type(_, ty) => {
+                quote! {
+                    ink_abi::ReturnTypeSpec::new::<#ty>()
+                }
+            }
+        };
+        quote! {
+            ink_abi::MessageSpec::new(#name)
+                .selector(#selector)
+                .mutates(#is_mut)
+                .args(vec![
+                    #(#inputs ,)*
+                ])
+                .docs(vec![
+                    #(#docs ,)*
+                ])
+                .returns(
+                    #ret_type
+                )
+                .done()
+        }
+    })
+}
+
+fn generate_abi_events<'a>(
+    contract: &'a hir::Contract,
+) -> impl Iterator<Item = TokenStream2> + 'a {
+    contract.events.iter().map(|event| {
+        let name = &event.ident;
+        let args = event.args.iter().map(|event_arg| {
+            let name = &event_arg.ident;
+            let indexed = event_arg.is_indexed();
+            let ty = &event_arg.ty;
+            quote! {
+                ink_abi::EventParamSpec::new::<#ty>(stringify!(#name), #indexed)
+            }
+        });
+        let docs = event.docs().map(trim_doc_string);
+        quote! {
+            ink_abi::EventSpec::new(stringify!(#name))
+                .args(vec![
+                    #(#args ,)*
+                ])
+                .docs(vec![
+                    #(#docs ,)*
+                ])
+                .done()
+        }
+    })
+}
+
+fn generate_abi_contract(contract: &hir::Contract) -> TokenStream2 {
+    let contract_name = &contract.name;
+    let contract_name_lit = contract_name.to_string();
+
+    // We currently do not have a way to specify docs for whole contracts.
+    // For this we could either take the docs of the contract state struct
+    // or allow for inner-attribute doc style within the `contract!` macro call.
+    let docs = quote! {};
+
+    let deploy_handler = generate_abi_deploy_handler(contract);
+    let messages = generate_abi_messages(contract);
+    let events = generate_abi_events(contract);
+
+    quote! {
+        ink_abi::ContractSpec::new(#contract_name_lit)
+            .on_deploy(
+                #deploy_handler
+            )
+            .messages(vec![
+                #(#messages ,)*
+            ])
+            .events(vec![
+                #(#events ,)*
+            ])
+            .docs(vec![
+                #docs
+            ])
+            .done()
+    }
+}
+
+fn generate_abi_layout(contract: &hir::Contract) -> TokenStream2 {
+    let contract_name = &contract.name;
+    quote! {
+        unsafe {
+            use ink_core::storage::alloc::AllocateUsing as _;
+            use ink_abi::HasLayout as _;
+            #contract_name::allocate_using(
+                &mut ink_core::storage::alloc::BumpAlloc::from_raw_parts(ink_core::storage::Key([0x0; 32]))
+            ).layout()
+        }
+    }
+}
diff --git a/lang/src/gen/build.rs b/lang/src/gen/build.rs
index 1f12533c89..823368f79b 100644
--- a/lang/src/gen/build.rs
+++ b/lang/src/gen/build.rs
@@ -427,6 +427,13 @@ fn codegen_for_state(tokens: &mut TokenStream2, contract: &hir::Contract) {
     tokens.extend(quote! {
         ink_model::state! {
             #state_attrs_toks
+            #[cfg_attr(
+                feature = "ink-generate-abi",
+                derive(
+                    type_metadata::Metadata,
+                    ink_abi::HasLayout,
+                )
+            )]
             pub struct #name
                 #struct_fields_toks
         }
diff --git a/lang/src/gen/mod.rs b/lang/src/gen/mod.rs
index d918ac354b..c9af96b6a0 100644
--- a/lang/src/gen/mod.rs
+++ b/lang/src/gen/mod.rs
@@ -14,6 +14,7 @@
 // You should have received a copy of the GNU General Public License
 // along with ink!.  If not, see <http://www.gnu.org/licenses/>.
 
+mod abi;
 mod build;
 mod doc;
 mod test;
@@ -33,5 +34,6 @@ pub fn generate_code(contract: &hir::Contract) -> TokenStream2 {
     build::generate_code(&mut tokens, contract);
     doc::generate_code(&mut tokens, contract);
     test::generate_code(&mut tokens, contract);
+    abi::generate_code(&mut tokens, contract);
     tokens
 }
diff --git a/lang/src/hir.rs b/lang/src/hir.rs
index 8b66816a55..0fabb4a9bb 100644
--- a/lang/src/hir.rs
+++ b/lang/src/hir.rs
@@ -54,6 +54,22 @@ pub struct Event {
     pub args: Punctuated<ast::EventArg, Token![,]>,
 }
 
+/// Returns an iterator over all doc attributes.
+pub fn filter_doc_attributes(
+    attrs: &[syn::Attribute],
+) -> impl Iterator<Item = &syn::Attribute> {
+    attrs
+        .iter()
+        .filter(|attr| attr.style == syn::AttrStyle::Outer && attr.path.is_ident("doc"))
+}
+
+impl Event {
+    /// Returns all doc attributes of the message.
+    pub fn docs(&self) -> impl Iterator<Item = &syn::Attribute> {
+        filter_doc_attributes(&self.attrs)
+    }
+}
+
 impl Contract {
     /// Extracts the type for environment types from the contract items
     /// and performs some integrity checks on it.
@@ -412,6 +428,11 @@ pub struct DeployHandler {
 }
 
 impl DeployHandler {
+    /// Returns all doc attributes of the message.
+    pub fn docs(&self) -> impl Iterator<Item = &syn::Attribute> {
+        filter_doc_attributes(&self.attrs)
+    }
+
     /// Converts this on-deploy handler into its corresponding message.
     pub fn into_message(self) -> Message {
         use crate::ident_ext::IdentExt as _;
@@ -461,6 +482,11 @@ pub struct Message {
 }
 
 impl Message {
+    /// Returns all doc attributes of the message.
+    pub fn docs(&self) -> impl Iterator<Item = &syn::Attribute> {
+        filter_doc_attributes(&self.attrs)
+    }
+
     /// Returns `true` if the message potentially mutates its state.
     pub fn is_mut(&self) -> bool {
         let self_arg = self
diff --git a/lang/src/lib.rs b/lang/src/lib.rs
index 88953fe62e..aa3c1b0f05 100644
--- a/lang/src/lib.rs
+++ b/lang/src/lib.rs
@@ -21,9 +21,6 @@ extern crate proc_macro;
 #[macro_use]
 mod error;
 
-#[cfg(feature = "generate-api-description")]
-mod api;
-
 mod ast;
 mod gen;
 mod hir;
diff --git a/scripts/check-examples.sh b/scripts/check-examples.sh
new file mode 100755
index 0000000000..98e47988e8
--- /dev/null
+++ b/scripts/check-examples.sh
@@ -0,0 +1,67 @@
+#!/bin/bash
+
+# Run this script from the workspace root!
+#
+# This script iterates through all examples under `examples/lang` folder
+# and runs the most important actions performed on an ink! smart contract:
+#
+# - compile to a Wasm blob using `no_std` environment
+# - run offchain tests `test-env`
+# - generate ABI file using `ink-generate-abi`
+#
+# Afterwards the script prints out a summary report.
+#
+# Exits with `0` if all tests completed successfully or `1` otherwise.
+
+declare -A results_test
+declare -A results_wasm
+declare -A results_abi
+
+all_checks_passed=0
+for example in $(ls examples/lang); do
+    cargo build --release --no-default-features --target=wasm32-unknown-unknown --verbose --manifest-path examples/lang/$example/Cargo.toml
+    result_wasm=$?
+    let "all_checks_passed |= $result_wasm"
+    if [ $result_wasm -eq 0 ]
+    then
+        results_wasm[$example]="ok"
+    else
+        results_wasm[$example]="ERROR"
+    fi
+    cargo test --verbose --manifest-path examples/lang/$example/Cargo.toml
+    result_test=$?
+    let "all_checks_passed |= $result_test"
+    if [ $result_test -eq 0 ]
+    then
+        results_test[$example]="ok"
+    else
+        results_test[$example]="ERROR"
+    fi
+    cargo run --package abi-gen --manifest-path examples/lang/$example/Cargo.toml
+    result_abi=$?
+    let "all_checks_passed |= $result_abi"
+    if [ $result_abi -eq 0 ]
+    then
+        results_abi[$example]="ok"
+    else
+        results_abi[$example]="ERROR"
+    fi
+done
+
+echo "Example Results"
+echo "---------------"
+for entry in ${!results_wasm[@]}; do
+    echo "- $entry (wasm): ${results_wasm[$entry]}"
+    echo "- $entry (test): ${results_test[$entry]}"
+    echo "- $entry (abi) : ${results_abi[$entry]}"
+done
+echo ""
+if [ $all_checks_passed -eq 0 ]
+then
+    echo "examples: All checks passed"
+    exit 0
+else
+    echo "examples: Some checks failed"
+    exit 1
+fi
+echo "---------------"
diff --git a/scripts/check-workspace.sh b/scripts/check-workspace.sh
new file mode 100755
index 0000000000..53a6edaf40
--- /dev/null
+++ b/scripts/check-workspace.sh
@@ -0,0 +1,50 @@
+#!/bin/bash
+
+declare -A results
+
+cargo check --verbose --all --all-features
+results["check_all_features"]=$?
+
+cargo check --verbose --all --no-default-features
+results["check_no_defaults"]=$?
+
+cargo fmt --verbose --all -- --check
+results["fmt"]=$?
+
+cargo clippy --verbose --all --all-features -- -D warnings
+results["clippy_all_features"]=$?
+
+cargo clippy --verbose --all --no-default-features -- -D warnings
+results["clippy_no_defaults"]=$?
+
+cargo test --verbose --all --all-features
+results["test_all_features"]=$?
+
+cargo build --verbose --all --no-default-features --release --target=wasm32-unknown-unknown
+results["build_wasm"]=$?
+
+all_checks_passed=0
+
+echo "Workspace Results"
+echo "-----------------"
+for entry in ${!results[@]}; do
+    result_str=""
+    if [ ${results[$entry]} -eq 0 ]
+    then
+        result_str="ok"
+    else
+        result_str="ERROR"
+    fi
+    echo "- $entry: $result_str"
+    let "all_checks_passed |= ${results[$entry]}"
+done
+echo ""
+if [ $all_checks_passed -eq 0 ]
+then
+    echo "workspace: All checks passed"
+    exit 0
+else
+    echo "workspace: Some checks failed"
+    exit 1
+fi
+echo "-----------------"
diff --git a/utils/src/lib.rs b/utils/src/lib.rs
index 4d52753341..d3b57aa67e 100644
--- a/utils/src/lib.rs
+++ b/utils/src/lib.rs
@@ -14,11 +14,11 @@
 // You should have received a copy of the GNU General Public License
 // along with ink!.  If not, see <http://www.gnu.org/licenses/>.
 
-//! Utilities in use by pDSL.
+//! Utilities in use by ink!.
 //!
-//! These are kept separate from pDSL core to allow for more dynamic inter crate dependencies.
+//! These are kept separate from ink core utilities to allow for more dynamic inter-crate dependencies.
 //! The main problem is that today Cargo manages crate features on a per-crate basis instead of
-//! a per-crate-target basis thus making dependencies from `ink_lang` to `ink_core` impossible.
+//! a per-crate-target basis thus making dependencies from `ink_lang` (or others) to `ink_core` impossible.
 //!
 //! By introducing `ink_utils` we have a way to share utility components between `ink_core` and
 //! other parts of the framework, like `ink_lang`.


Test patch:
diff --git a/lang/src/tests/events.rs b/lang/src/tests/events.rs
index fd11222bc3..8bb3e97b5a 100644
--- a/lang/src/tests/events.rs
+++ b/lang/src/tests/events.rs
@@ -89,6 +89,10 @@ fn contract_compiles() {
 
             ink_model::state! {
                 /// Tests emitting of custom defined events.
+                #[cfg_attr(
+                    feature = "ink-generate-abi",
+                    derive(type_metadata::Metadata, ink_abi::HasLayout,)
+                )]
                 pub struct CallCounter {
                     /// A simple counter for the calls.
                     count: storage::Value<u32>,
@@ -286,6 +290,77 @@ fn contract_compiles() {
                     }
                 }
             }
+
+            #[cfg(feature = "ink-generate-abi")]
+            pub fn ink_generate_abi() -> ink_abi::InkProject {
+                let contract = {
+                    ink_abi::ContractSpec::new("CallCounter")
+                        .on_deploy(ink_abi::DeploySpec::new()
+                        .args(vec![])
+                        .docs(vec![])
+                        .done())
+                        .messages(vec![
+                            ink_abi::MessageSpec::new("inc")
+                                .selector(257544423u32)
+                                .mutates(true)
+                                .args(vec![])
+                                .docs(vec![
+                                    "Increments the internal counter.",
+                                    "",
+                                    "# Note",
+                                    "",
+                                    "Also emits an event.",
+                                ])
+                                .returns(
+                                    ink_abi::ReturnTypeSpec::none()
+                                )
+                                .done(),
+                            ink_abi::MessageSpec::new("dec")
+                                .selector(1772705147u32)
+                                .mutates(true)
+                                .args(vec![])
+                                .docs(vec![
+                                    "Decrements the internal counter.",
+                                    "",
+                                    "# Note",
+                                    "",
+                                    "Also emits an event.",
+                                ])
+                                .returns(ink_abi::ReturnTypeSpec::none())
+                                .done(),
+                        ])
+                        .events(vec![
+                            ink_abi::EventSpec::new(stringify!(DecCalled))
+                                .args(vec![
+                                    ink_abi::EventParamSpec::new::<u32>(stringify!(current), false),
+                                ])
+                                .docs(vec![
+                                    "Fires when the value is decremented.",
+                                ])
+                                .done(),
+                            ink_abi::EventSpec::new(stringify!(IncCalled))
+                                .args(vec![ink_abi::EventParamSpec::new::<u32>(stringify!(current), false),
+                            ])
+                            .docs(vec![
+                                "Fires when the value is incremented.",
+                            ])
+                            .done(),
+                        ])
+                        .docs(vec![])
+                        .done()
+                };
+                let layout = {
+                    unsafe {
+                        use ink_core::storage::alloc::AllocateUsing as _;
+                        use ink_abi::HasLayout as _;
+                        CallCounter::allocate_using(
+                            &mut ink_core::storage::alloc::BumpAlloc::from_raw_parts(
+                                ink_core::storage::Key([0x0; 32])))
+                                .layout()
+                    }
+                };
+                ink_abi::InkProject::new(layout, contract)
+            }
         },
     )
 }
diff --git a/lang/src/tests/flipper.rs b/lang/src/tests/flipper.rs
index f6c2b0ecdb..724e65be22 100644
--- a/lang/src/tests/flipper.rs
+++ b/lang/src/tests/flipper.rs
@@ -69,6 +69,10 @@ fn contract_compiles() {
 
             ink_model::state! {
                 /// A simple contract that has a boolean value that can be flipped and be returned.
+                #[cfg_attr(
+                    feature = "ink-generate-abi",
+                    derive(type_metadata::Metadata, ink_abi::HasLayout,)
+                )]
                 pub struct Flipper {
                     /// The internal value.
                     value: storage::Value<bool>,
@@ -184,6 +188,51 @@ fn contract_compiles() {
                     }
                 }
             }
+
+            #[cfg(feature = "ink-generate-abi")]
+            pub fn ink_generate_abi() -> ink_abi::InkProject{
+                let contract = {
+                    ink_abi::ContractSpec::new("Flipper")
+                        .on_deploy(ink_abi::DeploySpec::new()
+                        .args(vec![])
+                        .docs(vec!["The internal boolean is initialized with `true`."])
+                        .done()
+                    )
+                    .messages(vec![
+                        ink_abi::MessageSpec::new("flip")
+                            .selector(970692492u32)
+                            .mutates(true)
+                            .args(vec![])
+                            .docs(vec!["Flips the internal boolean.",])
+                            .returns(ink_abi::ReturnTypeSpec::none())
+                            .done(),
+                        ink_abi::MessageSpec::new("get")
+                            .selector(4266279973u32)
+                            .mutates(false)
+                            .args(vec![])
+                            .docs(vec!["Returns the internal boolean.",])
+                            .returns(
+                                ink_abi::ReturnTypeSpec::new::<bool>()
+                            )
+                            .done(),
+                        ])
+                        .events(vec![])
+                        .docs(vec![])
+                        .done()
+                };
+                let layout = {
+                    unsafe {
+                        use ink_core::storage::alloc::AllocateUsing as _;
+                        use ink_abi::HasLayout as _;
+                        Flipper::allocate_using(
+                            &mut ink_core::storage::alloc::BumpAlloc::from_raw_parts(
+                                ink_core::storage::Key([0x0; 32])
+                            )
+                        ).layout()
+                    }
+                };
+                ink_abi::InkProject::new(layout, contract)
+            }
         },
     )
 }
diff --git a/lang/src/tests/incrementer.rs b/lang/src/tests/incrementer.rs
index 2b3dcb5079..70f52d1a1d 100644
--- a/lang/src/tests/incrementer.rs
+++ b/lang/src/tests/incrementer.rs
@@ -76,6 +76,10 @@ fn contract_compiles() {
             ink_model::state! {
                 /// A simple contract that has a value that can be
                 /// incremented, returned and compared.
+                #[cfg_attr(
+                    feature = "ink-generate-abi",
+                    derive(type_metadata::Metadata, ink_abi::HasLayout,)
+                )]
                 pub struct Incrementer {
                     /// The internal value.
                     value: storage::Value<u32>,
@@ -207,6 +211,75 @@ fn contract_compiles() {
                     }
                 }
             }
+
+            #[cfg(feature = "ink-generate-abi")]
+            pub fn ink_generate_abi() -> ink_abi::InkProject {
+                let contract = {
+                    ink_abi::ContractSpec::new("Incrementer")
+                        .on_deploy(ink_abi::DeploySpec::new()
+                            .args(vec![
+                                ink_abi::MessageParamSpec::new::<u32>("init_value")
+                            ])
+                            .docs(vec![
+                                "Automatically called when the contract is deployed."
+                            ])
+                            .done()
+                        )
+                        .messages(vec![
+                            ink_abi::MessageSpec::new("inc")
+                                .selector(257544423u32)
+                                .mutates(true)
+                                .args(vec![
+                                    ink_abi::MessageParamSpec::new::<u32>("by"),
+                                ])
+                                .docs(vec![
+                                    "Increments the internal counter.",
+                                ])
+                                .returns(
+                                    ink_abi::ReturnTypeSpec::none()
+                                )
+                                .done(),
+                            ink_abi::MessageSpec::new("get")
+                                .selector(4266279973u32)
+                                .mutates(false)
+                                .args(vec![])
+                                .docs(vec![
+                                    "Returns the internal counter.",
+                                ])
+                                .returns(
+                                    ink_abi::ReturnTypeSpec::new::<u32>()
+                                )
+                                .done(),
+                            ink_abi::MessageSpec::new("compare")
+                                .selector(363906316u32)
+                                .mutates(false)
+                                .args(vec![ink_abi::MessageParamSpec::new::<u32>("x"),
+                            ])
+                            .docs(vec![
+                                "Returns `true` if `x` is greater than the internal value.",
+                            ])
+                            .returns(
+                                ink_abi::ReturnTypeSpec::new::<bool>()
+                            )
+                            .done(),
+                        ])
+                        .events(vec![])
+                        .docs(vec![])
+                        .done()
+                };
+                let layout = {
+                    unsafe {
+                        use ink_core::storage::alloc::AllocateUsing as _;
+                        use ink_abi::HasLayout as _;
+                        Incrementer::allocate_using(
+                            &mut ink_core::storage::alloc::BumpAlloc::from_raw_parts(
+                                ink_core::storage::Key([0x0; 32])
+                            )
+                        ).layout()
+                    }
+                };
+                ink_abi::InkProject::new(layout, contract)
+            }
         },
     )
 }
diff --git a/lang/src/tests/noop.rs b/lang/src/tests/noop.rs
index b86ca78435..d540740915 100644
--- a/lang/src/tests/noop.rs
+++ b/lang/src/tests/noop.rs
@@ -63,6 +63,10 @@ fn contract_compiles() {
                 /// # Note
                 ///
                 /// Can be deployed, cannot be called.
+                #[cfg_attr(
+                    feature = "ink-generate-abi",
+                    derive(type_metadata::Metadata, ink_abi::HasLayout,)
+                )]
                 pub struct Noop {}
             }
 
@@ -140,6 +144,35 @@ fn contract_compiles() {
 
                 impl TestableNoop { }
             }
+
+            #[cfg(feature = "ink-generate-abi")]
+            pub fn ink_generate_abi() -> ink_abi::InkProject {
+                let contract = {
+                    ink_abi::ContractSpec::new("Noop")
+                        .on_deploy(ink_abi::DeploySpec::new()
+                        .args(vec![])
+                        .docs(vec![
+                            "Does nothing to initialize itself."
+                        ])
+                        .done()
+                    )
+                    .messages(vec![])
+                    .events(vec![])
+                    .docs(vec![])
+                    .done()
+                };
+                let layout = {
+                    unsafe {
+                        use ink_core::storage::alloc::AllocateUsing as _;
+                        use ink_abi::HasLayout as _;
+                        Noop::allocate_using(
+                            &mut ink_core::storage::alloc::BumpAlloc::from_raw_parts(ink_core::storage::Key(
+                                [0x0; 32]
+                            ))).layout()
+                        }
+                    };
+                    ink_abi::InkProject::new(layout, contract)
+                }
         },
     )
 }