diff --git a/scripts/wasmd/README.md b/scripts/wasmd/README.md
new file mode 100644
index 00000000..6465ca76
--- /dev/null
+++ b/scripts/wasmd/README.md
@@ -0,0 +1,152 @@
+# Local Stargate development network with CosmWasm support
+
+## Starting the blockchain
+
+Run the following:
+
+```
+cd scripts/wasmd
+./start.sh && ./init.sh
+```
+
+## CLI
+
+Docker-friendly access to `wasmd` is provided. Just use the `./cli.sh` script.
+For example:
+
+```
+./cli.sh status
+```
+
+This should give you output similar to the following if your blockchain is
+running:
+
+```json
+{
+ "node_info": {
+ "protocol_version": { "p2p": "7", "block": "10", "app": "0" },
+ "id": "223aedddd9442bcf16641858ca85837f27997d0d",
+ "listen_addr": "tcp://0.0.0.0:26656",
+ "network": "testing",
+ "version": "0.32.2",
+ "channels": "4020212223303800",
+ "moniker": "testing",
+ "other": { "tx_index": "on", "rpc_address": "tcp://127.0.0.1:26657" }
+ },
+ "sync_info": {
+ "latest_block_hash": "3E3BEBCFA4E47BC67C7DE44DD4E83D8D42235DE75DA942A6BECD1F0F5A6246E4",
+ "latest_app_hash": "73A3641BDEFBB728B1B48FB87B510F3E76E3B4519BC4954C6E1060738FCE8B14",
+ "latest_block_height": "1217",
+ "latest_block_time": "2019-09-26T15:44:13.0111312Z",
+ "catching_up": false
+ },
+ "validator_info": {
+ "address": "3A7EBE1A9E333146AE5D9FCB765B88BDD4D2859A",
+ "pub_key": {
+ "type": "tendermint/PubKeyEd25519",
+ "value": "3ZYx1HKwT/llXzYC2yVeWEiWHd6uBQ7Bi7jiDFczx28="
+ },
+ "voting_power": "100"
+ }
+}
+```
+
+## Adding the validator key to your keybase
+
+The Cosmos test network is initialised with a validator (see
+`.gaiad/config/genesis.json`). This validator has the following mnemonic:
+
+```
+economy stock theory fatal elder harbor betray wasp final emotion task crumble siren bottom lizard educate guess current outdoor pair theory focus wife stone
+```
+
+To add the validator key to your local keybase run the following, choose an
+encryption passphrase (e.g. `testing123`) and enter the above mnemonic when
+prompted:
+
+```
+./cli.sh keys add validator --recover
+```
+
+You should get output matching the following:
+
+```
+- name: validator
+ type: local
+ address: cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6
+ pubkey: cosmospub1addwnpepqd8sgxq7aw348ydctp3n5ajufgxp395hksxjzc6565yfp56scupfqhlgyg5
+ mnemonic: ""
+ threshold: 0
+ pubkeys: []
+```
+
+## Preset accounts
+
+1. **Faucet**
+ economy stock theory fatal elder harbor betray wasp final emotion task crumble siren bottom lizard educate guess current outdoor pair theory focus wife stone
+ Address 0: cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6
+ Address 1: cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5
+ Address 2: cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k
+ Address 3: cosmos142u9fgcjdlycfcez3lw8x6x5h7rfjlnfhpw2lx
+ Address 4: cosmos1hsm76p4ahyhl5yh3ve9ur49r5kemhp2r0dcjvx
+ Pubkey 0: A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ
+ Pubkey 1: AiDosfIbBi54XJ1QjCeApumcy/FjdtF+YhywPf3DKTx7
+ Pubkey 2: AzQg33JZqH7vSsm09esZY5bZvmzYwE/SY78cA0iLxpD7
+ Pubkey 3: A3gOAlB6aiRTCPvWMQg2+ZbGYNsLd8qlvV28m8p2UhY2
+ Pubkey 4: Aum2063ub/ErUnIUB36sK55LktGUStgcbSiaAnL1wadu
+2. **Alice**: Test account for the cosmwasm package that can run in parallel with faucet without sequence conflicts
+ enlist hip relief stomach skate base shallow young switch frequent cry park
+ Address 0: cosmos14qemq0vw6y3gc3u3e0aty2e764u4gs5le3hada
+ Address 1: cosmos1hhg2rlu9jscacku2wwckws7932qqqu8x3gfgw0
+ Address 2: cosmos1xv9tklw7d82sezh9haa573wufgy59vmwe6xxe5
+ Address 3: cosmos17yg9mssjenmc3jkqth6ulcwj9cxujrxxzezwta
+ Address 4: cosmos1f7j7ryulwjfe9ljplvhtcaxa6wqgula3etktce
+ Pubkey 0: A9cXhWb8ZpqCzkA8dQCPV29KdeRLV3rUYxrkHudLbQtS
+ Pubkey 1: A4XluzvcUx0ViLF0DjYW5/noArGwpltDstoUUZo+g1b0
+ Pubkey 2: A5TKr1NKc/MKRJ7+EHDD9PlzmGaPD/di/6hzZyBwxoy5
+ Pubkey 3: A/HSABDUqMB2qDy+PA7fiuuuA+hfrco2VwwiThMiTzUx
+ Pubkey 4: A7usTiqgqfxL/WKhoephDUSCHBQlLagtwI/qTmEteTRM
+3. **Bob**: Test account (unused for now)
+ remain fragile remove stamp quiz bus country dress critic mammal office need
+ Address 0: cosmos1lvrwcvrqlc5ktzp2c4t22xgkx29q3y83lktgzl
+ Address 1: cosmos1vkv9sfwaak76weyamqx0flmng2vuquxqcuqukh
+ Address 2: cosmos106jwym4s9aujcmes26myzzwqsccw09sdm0v5au
+ Address 3: cosmos1c7wpeen2uv8thayf7g8q2rgpm29clj0dgrdtzw
+ Address 4: cosmos1mjxpv9ft30wer7ma7kwfxhm42l379xutplrdk6
+ Pubkey 0: A0d/GxY+UALE+miWJP0qyq4/EayG1G6tsg24v+cbD6By
+ Pubkey 1: Agqd6njsVEQD1CR+F2aqEb8hil5NXZ06mjKgetaNC12t
+ Pubkey 2: A6e9ElvKaM0DKWh1bIdK3bgB14dyEDgIXYMA0Lbs1GoQ
+ Pubkey 3: AkAK5PQaucieWMb0+tTRY01feYI+upRnoNK556eD0Ibb
+ Pubkey 4: A5HMVEAJsupdQWItbZv5Z1xZifDixQi6tjU/hJpZY1bF
+4. **Unused**: for testing account state; this account never changes balances or sequences
+ oyster design unusual machine spread century engine gravity focus cave carry slot
+ ArkCaFUJ/IH+vKBmNRCdUVl3mCAhbopk9jjW4Ko4OfRQ
+ cosmos1cjsxept9rkggzxztslae9ndgpdyt2408lk850u
+5. **Guest**: account for manual testing
+ degree tackle suggest window test behind mesh extra cover prepare oak script
+ Am/+YV0LaeqQPu7BDJuDHV7J8y68ptkGs10YS+9s71Nq
+ cosmos17d0jcz59jf68g52vq38tuuncmwwjk42u6mcxej
+6. **Ledger**: accounts for Ledger based demos and tests
+ example indicate trick cereal hub fix civil host kiss version bird dash
+ Address 0: cosmos1p6xs63q4g7np99ttv5nd3yzkt8n4qxa47w8aea
+ Address 1: cosmos1meeu3jl268txxytwmmrsljk8rawh6n2majstn2
+ Address 2: cosmos1cak6lnpfxs035xd88sq8e4zujsm8g2g97dxu5c
+ Address 3: cosmos1x3x8kyypx8z6q7fx3gw65x29mhl5gg8qp4ynlr
+ Address 4: cosmos18c27m2rj4lg74md03ujralvt562c097n8zpdf9
+ Address 5: cosmos1q2y53e6x7s5mlddtd2qkcjr3nwr4dszv6fr9rt
+ Address 6: cosmos1paa2gstlk7c98n27dw2g6tp6fyqvf32mm67qz3
+ Address 7: cosmos1rvxjd8k6xvssz2eerfzemvat35pttfgr67yyzd
+ Address 8: cosmos12zejt8d9xl70jd2333p4p265m2nr9h8gsaewk0
+ Address 9: cosmos1exctm2036jtwyc9v3ftqfzmgnv9tdhj26v87uh
+ Address 10: cosmos1f3pws3ztnp3s4nn5zxqdrl9vlqv5avkqmlrus4
+ Pubkey 0: A66JoCNaNSXDsyj4qW7JgqXPTz5rOnfE6EKEArf4jJEK
+ Pubkey 1: AtvmGuZvEN3NwL05BQdxl3XygUf+Vl/930fhFMt1HTyU
+ Pubkey 2: A58dfmfVoKoTCteEzTHBC0OLJIBgzejGDVVEb8YW9vtJ
+ Pubkey 3: A1wA01EixwcWJkdhI69ckGuQDX0NimhLCYdrQCegkOJF
+ Pubkey 4: A9juq+VbP26qtVh71ANlwwJQ+ABTWIyHEKYrVwjmbYE6
+ Pubkey 5: Ar4VUqiRYl75+TF3AExX8at3deeLj2O9mNMtBq2aVpym
+ Pubkey 6: Ak/JoSXzu6+Rp2W0wT6CqfZfzlDOwebl7xVF/zmKX99Y
+ Pubkey 7: AtmLZZGHeCiNuroPAzBK2NKeXKT68SwioLj4I8Oj35Mn
+ Pubkey 8: AuaUr9GEMUBKeZrJD/dv9QL/zJmMxX7OA/sjRrvBFXS2
+ Pubkey 9: AiV5uMzvzoD7hlF+GhYuRCnf8tP+0AlPMbtfVoYv3InI
+ Pubkey 10: A2ZnLEcbpyjS30H5UF1vezq29aBcT9oo5EARATIW9Cpj
diff --git a/scripts/wasmd/cli.sh b/scripts/wasmd/cli.sh
new file mode 100755
index 00000000..ed3a19f6
--- /dev/null
+++ b/scripts/wasmd/cli.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+set -o errexit -o nounset -o pipefail
+command -v shellcheck > /dev/null && shellcheck "$0"
+
+SCRIPT_DIR="$(realpath "$(dirname "$0")")"
+# shellcheck source=./env
+# shellcheck disable=SC1091
+source "$SCRIPT_DIR"/env
+
+# TODO: make this run as UID? Does this matter?
+HOME_DIR="/root"
+
+docker run \
+ --rm \
+ -it \
+ --mount type=volume,source=wasmd_data,target=/root/.wasmd \
+ -w "$HOME_DIR" \
+ --env "HOME=$HOME_DIR" \
+ --net "container:$CONTAINER_NAME" \
+ "$REPOSITORY:$VERSION" \
+ wasmd "$@"
diff --git a/scripts/wasmd/env b/scripts/wasmd/env
new file mode 100644
index 00000000..cfb4b029
--- /dev/null
+++ b/scripts/wasmd/env
@@ -0,0 +1,5 @@
+# Choose from https://hub.docker.com/r/cosmwasm/wasmd/tags
+REPOSITORY="cosmwasm/wasmd"
+VERSION="v0.12.0"
+
+CONTAINER_NAME="wasmd"
diff --git a/scripts/wasmd/generate_template.sh b/scripts/wasmd/generate_template.sh
new file mode 100755
index 00000000..b2b981b7
--- /dev/null
+++ b/scripts/wasmd/generate_template.sh
@@ -0,0 +1,44 @@
+#!/bin/bash
+set -o errexit -o nounset -o pipefail
+command -v shellcheck > /dev/null && shellcheck "$0"
+
+SCRIPT_DIR="$(realpath "$(dirname "$0")")"
+# shellcheck source=./env
+# shellcheck disable=SC1091
+source "$SCRIPT_DIR"/env
+
+rm -rf "$SCRIPT_DIR/template"
+mkdir "$SCRIPT_DIR/template"
+
+# The usage of the accounts below is documented in README.md of this directory
+docker run --rm \
+ -e PASSWORD=my-secret-password \
+ --mount type=bind,source="$SCRIPT_DIR/template",target=/root \
+ "$REPOSITORY:$VERSION" \
+ ./setup_wasmd.sh \
+ cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6 cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5 cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k cosmos142u9fgcjdlycfcez3lw8x6x5h7rfjlnfhpw2lx cosmos1hsm76p4ahyhl5yh3ve9ur49r5kemhp2r0dcjvx \
+ cosmos14qemq0vw6y3gc3u3e0aty2e764u4gs5le3hada cosmos1hhg2rlu9jscacku2wwckws7932qqqu8x3gfgw0 cosmos1xv9tklw7d82sezh9haa573wufgy59vmwe6xxe5 cosmos17yg9mssjenmc3jkqth6ulcwj9cxujrxxzezwta cosmos1f7j7ryulwjfe9ljplvhtcaxa6wqgula3etktce \
+ cosmos1lvrwcvrqlc5ktzp2c4t22xgkx29q3y83lktgzl cosmos1vkv9sfwaak76weyamqx0flmng2vuquxqcuqukh cosmos106jwym4s9aujcmes26myzzwqsccw09sdm0v5au cosmos1c7wpeen2uv8thayf7g8q2rgpm29clj0dgrdtzw cosmos1mjxpv9ft30wer7ma7kwfxhm42l379xutplrdk6 \
+ cosmos1cjsxept9rkggzxztslae9ndgpdyt2408lk850u \
+ cosmos17d0jcz59jf68g52vq38tuuncmwwjk42u6mcxej \
+ cosmos1p6xs63q4g7np99ttv5nd3yzkt8n4qxa47w8aea cosmos1meeu3jl268txxytwmmrsljk8rawh6n2majstn2 cosmos1cak6lnpfxs035xd88sq8e4zujsm8g2g97dxu5c cosmos1x3x8kyypx8z6q7fx3gw65x29mhl5gg8qp4ynlr cosmos18c27m2rj4lg74md03ujralvt562c097n8zpdf9 cosmos1q2y53e6x7s5mlddtd2qkcjr3nwr4dszv6fr9rt cosmos1paa2gstlk7c98n27dw2g6tp6fyqvf32mm67qz3 cosmos1rvxjd8k6xvssz2eerfzemvat35pttfgr67yyzd cosmos12zejt8d9xl70jd2333p4p265m2nr9h8gsaewk0 cosmos1exctm2036jtwyc9v3ftqfzmgnv9tdhj26v87uh cosmos1f3pws3ztnp3s4nn5zxqdrl9vlqv5avkqmlrus4
+
+# The ./template folder is created by the docker daemon's user (root on Linux, current user
+# when using Docker Desktop on macOS), let's make it ours if needed
+if [ ! -x "$SCRIPT_DIR/template/.wasmd/config/gentx" ]; then
+ sudo chown -R "$(id -u):$(id -g)" "$SCRIPT_DIR/template"
+fi
+
+function inline_jq() {
+ IN_OUT_PATH="$1"
+ shift
+ TMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/inline_jq.XXXXXXXXX")
+ TMP_FILE="$TMP_DIR/$(basename "$IN_OUT_PATH")"
+ jq "$@" < "$IN_OUT_PATH" > "$TMP_FILE"
+ if ! mv "$TMP_FILE" "$IN_OUT_PATH" ; then
+ >&2 echo "Temp file '$TMP_FILE' could not be deleted. If it contains sensitive data, you might want to delete it manually."
+ exit 3
+ fi
+}
+
+inline_jq "$SCRIPT_DIR/template/.wasmd/config/genesis.json" -S
diff --git a/scripts/wasmd/init.sh b/scripts/wasmd/init.sh
new file mode 100755
index 00000000..7c49cb3e
--- /dev/null
+++ b/scripts/wasmd/init.sh
@@ -0,0 +1,30 @@
+#!/bin/bash
+set -o errexit -o nounset -o pipefail
+command -v shellcheck > /dev/null && shellcheck "$0"
+
+echo "Waiting for blockchain and REST server to be available ..."
+timeout 60 bash -c "until curl -s http://localhost:1317/node_info > /dev/null; do sleep 0.5; done"
+# The chain is unreliable in the first second of its existence (https://gist.github.com/webmaster128/8175692d4af5e6c572fddda7a9ef437c)
+sleep 1
+echo "Okay, thank you for your patience."
+
+SCRIPT_DIR="$(realpath "$(dirname "$0")")"
+
+
+#
+# Cosmos SDK init
+#
+"$SCRIPT_DIR/send_first.js"
+
+#
+# CosmWasm init
+#
+(
+ echo "Ensuring contracts' checksums are correct ..."
+ cd "$SCRIPT_DIR/contracts"
+ sha256sum --check checksums.sha256
+)
+"$SCRIPT_DIR/deploy_hackatom.js"
+"$SCRIPT_DIR/deploy_erc20.js"
+"$SCRIPT_DIR/deploy_cw3.js"
+# "$SCRIPT_DIR/deploy_nameservice.js"
diff --git a/scripts/wasmd/priv_validator_state.template.json b/scripts/wasmd/priv_validator_state.template.json
new file mode 100644
index 00000000..0967ef42
--- /dev/null
+++ b/scripts/wasmd/priv_validator_state.template.json
@@ -0,0 +1 @@
+{}
diff --git a/scripts/wasmd/send_first.js b/scripts/wasmd/send_first.js
new file mode 100755
index 00000000..228a255d
--- /dev/null
+++ b/scripts/wasmd/send_first.js
@@ -0,0 +1,36 @@
+#!/usr/bin/env node
+
+/* eslint-disable @typescript-eslint/naming-convention */
+const { Random } = require("@cosmjs/crypto");
+const { Bech32 } = require("@cosmjs/encoding");
+const {
+ coins,
+ Secp256k1HdWallet,
+ SigningCosmosClient,
+ assertIsBroadcastTxSuccess,
+} = require("@cosmjs/stargate");
+
+const httpUrl = "http://localhost:1317";
+const faucet = {
+ mnemonic:
+ "economy stock theory fatal elder harbor betray wasp final emotion task crumble siren bottom lizard educate guess current outdoor pair theory focus wife stone",
+ address0: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6",
+};
+
+async function main() {
+ const wallet = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic);
+ const client = new SigningCosmosClient(httpUrl, faucet.address0, wallet);
+ const recipient = Bech32.encode("cosmos", Random.getBytes(20));
+ const amount = coins(226644, "ucosm");
+ const memo = "Ensure chain has my pubkey";
+ const sendResult = await client.sendTokens(recipient, amount, memo);
+ assertIsBroadcastTxSuccess(sendResult);
+}
+
+main().then(
+ () => process.exit(0),
+ (error) => {
+ console.error(error);
+ process.exit(1);
+ },
+);
diff --git a/scripts/wasmd/start.sh b/scripts/wasmd/start.sh
new file mode 100755
index 00000000..99a6c244
--- /dev/null
+++ b/scripts/wasmd/start.sh
@@ -0,0 +1,67 @@
+#!/bin/bash
+set -o errexit -o nounset -o pipefail
+command -v shellcheck > /dev/null && shellcheck "$0"
+
+# Please keep this in sync with the Ports overview in HACKING.md
+# Tendermint port (26657) and p2p port (26656) are not exposed since we don't need them for testing
+LCD_API_PORT_GUEST="1317"
+LCD_API_PORT_HOST="1317"
+
+SCRIPT_DIR="$(realpath "$(dirname "$0")")"
+# shellcheck source=./env
+# shellcheck disable=SC1091
+source "$SCRIPT_DIR"/env
+
+TMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/wasmd.XXXXXXXXX")
+chmod 777 "$TMP_DIR"
+echo "Using temporary dir $TMP_DIR"
+WASMD_LOGFILE="$TMP_DIR/wasmd.log"
+# REST_SERVER_LOGFILE="$TMP_DIR/rest-server.log"
+
+# Use a fresh volume for every start
+docker volume rm -f wasmd_data
+
+# This starts up wasmd
+docker run --rm \
+ --name "$CONTAINER_NAME" \
+ -p "$LCD_API_PORT_HOST":"$LCD_API_PORT_GUEST" \
+ --mount type=bind,source="$SCRIPT_DIR/template",target=/template \
+ --mount type=volume,source=wasmd_data,target=/root \
+ "$REPOSITORY:$VERSION" \
+ ./run_wasmd.sh /template \
+ > "$WASMD_LOGFILE" &
+
+echo "wasmd running and logging into $WASMD_LOGFILE"
+
+# Debug chain start
+# sleep 3 && cat "$WASMD_LOGFILE"
+
+# Use a large timeout because of potentially long image download in `docker run`
+if ! timeout 180 bash -c "until [ \"\$( docker container inspect -f '{{.State.Status}}' \"$CONTAINER_NAME\" 2> /dev/null )\" = \"running\" ]; do sleep 0.5; done"; then
+ echo "Container named '$CONTAINER_NAME' not running. We cannot continue." \
+ "This can happen when 'docker run' needs too long to download and start." \
+ "It might be worth retrying this step once the image is in the local docker cache."
+ docker kill "$CONTAINER_NAME"
+ exit 1
+fi
+
+# docker exec "$CONTAINER_NAME" \
+# wasmcli rest-server \
+# --node tcp://localhost:26657 \
+# --trust-node \
+# --unsafe-cors \
+# --laddr "tcp://0.0.0.0:$LCD_API_PORT_GUEST" \
+# > "$REST_SERVER_LOGFILE" &
+
+# echo "rest server running on http://localhost:$LCD_API_PORT_HOST and logging into $REST_SERVER_LOGFILE"
+
+if [ -n "${CI:-}" ]; then
+ # Give process some time to come alive. No idea why this helps. Needed for CI.
+ sleep 0.5
+
+ # Follow the logs in CI's background job
+ tail -f "$WASMD_LOGFILE"
+fi
+
+# Debug rest server start
+# sleep 3 && cat "$REST_SERVER_LOGFILE"
diff --git a/scripts/wasmd/stop.sh b/scripts/wasmd/stop.sh
new file mode 100755
index 00000000..a318ce45
--- /dev/null
+++ b/scripts/wasmd/stop.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+set -o errexit -o nounset -o pipefail
+command -v shellcheck > /dev/null && shellcheck "$0"
+
+SCRIPT_DIR="$(realpath "$(dirname "$0")")"
+# shellcheck source=./env
+# shellcheck disable=SC1091
+source "$SCRIPT_DIR"/env
+
+echo "Killing Cosmos container..."
+docker container kill "$CONTAINER_NAME"