diff --git a/explorer-ui/src/contexts/chainNames.test.ts b/explorer-ui/src/contexts/chainNames.test.ts
index ee1e64b..1050b92 100644
--- a/explorer-ui/src/contexts/chainNames.test.ts
+++ b/explorer-ui/src/contexts/chainNames.test.ts
@@ -21,5 +21,5 @@ test("Shibuya chain info", () => {
 })
 
 test("watr testnet chain info", () => {
-  expect(resolveInfoFromName("watr network testnet")).toEqual("rococoWatr")
+  expect(resolveInfoFromName("watr testnet")).toEqual("rococoWatr")
 })
diff --git a/explorer-ui/src/contexts/chainNames.ts b/explorer-ui/src/contexts/chainNames.ts
index 39f8a44..3a05796 100644
--- a/explorer-ui/src/contexts/chainNames.ts
+++ b/explorer-ui/src/contexts/chainNames.ts
@@ -20,7 +20,7 @@ const names = [
     endpoint: "watr"
   },
   {
-    chain: "watr network testnet",
+    chain: "watr testnet",
     endpoint: "rococoWatr"
   },
   {
diff --git a/squid-ink/src/chain-config.ts b/squid-ink/src/chain-config.ts
index ca3945d..826d9ce 100644
--- a/squid-ink/src/chain-config.ts
+++ b/squid-ink/src/chain-config.ts
@@ -41,11 +41,11 @@ const chainConfig: Record<string, ChainConfig> = {
     ss58Format: 19,
     token: {
       tokenDecimals: 18,
-      tokenSymbol: "WATRD",
+      tokenSymbol: "WATR",
     },
   },
   watr_test: {
-    name: "Watr Network Testnet",
+    name: "Watr Testnet",
     ss58Format: 19,
     token: {
       tokenDecimals: 18,
diff --git a/squid-ink/src/chains/watrtestnet/normalised-types/calls.ts b/squid-ink/src/chains/watrtestnet/normalised-types/calls.ts
new file mode 100644
index 0000000..19d246e
--- /dev/null
+++ b/squid-ink/src/chains/watrtestnet/normalised-types/calls.ts
@@ -0,0 +1,27 @@
+import * as ss58 from "@subsquid/ss58";
+import { ResolvedContractsCallCall } from "chains/normalised-return-types";
+import { ss58Format } from "../../../chain-config";
+import { ContractsCallCall } from "../types/calls";
+
+export class NormalisedContractsCallCall extends ContractsCallCall {
+  resolve(): ResolvedContractsCallCall {
+    if (this.isWatrNodeV1000) {
+      const { dest, value, gasLimit, storageDepositLimit, data } =
+        this.asWatrNodeV1000;
+      // TODO: Ensure proper support of MultiAddress
+      if (dest.__kind === "Index") {
+        throw new Error("Multi-address of type Index is not supported!");
+      }
+      return {
+        contractAddress: ss58.codec(ss58Format).encode(dest.value),
+        value,
+        gasLimit: {
+          refTime: gasLimit,
+        },
+        storageDepositLimit,
+        data,
+      };
+    }
+    throw new Error("No Runtime version found");
+  }
+}
diff --git a/squid-ink/src/chains/watrtestnet/normalised-types/events.ts b/squid-ink/src/chains/watrtestnet/normalised-types/events.ts
new file mode 100644
index 0000000..89907cf
--- /dev/null
+++ b/squid-ink/src/chains/watrtestnet/normalised-types/events.ts
@@ -0,0 +1,189 @@
+// eslint-disable-next-line eslint-comments/disable-enable-pair
+/* eslint-disable max-classes-per-file */
+import * as ss58 from "@subsquid/ss58";
+import { toHex } from "@subsquid/util-internal-hex";
+import {
+  ResolvedBalancesEndowedEvent,
+  ResolvedBalancesReservedEvent,
+  ResolvedBalancesTransferEvent,
+  ResolvedBalancesWithdrawEvent,
+  ResolvedContractEmittedEvent,
+  ResolvedContractCodeRemovedEvent,
+  ResolvedContractsCodeStoredEvent,
+  ResolvedContractsCodeUpdatedEvent,
+  ResolvedContractsInstantiatedEvent,
+  ResolvedContractTerminatedEvent,
+  ResolvedNewAccountEvent,
+} from "chains/normalised-return-types";
+import { ss58Format } from "../../../chain-config";
+import {
+  BalancesEndowedEvent,
+  BalancesReservedEvent,
+  BalancesTransferEvent,
+  BalancesWithdrawEvent,
+  ContractsCodeRemovedEvent,
+  ContractsCodeStoredEvent,
+  ContractsContractCodeUpdatedEvent,
+  ContractsContractEmittedEvent,
+  ContractsInstantiatedEvent,
+  ContractsTerminatedEvent,
+  SystemNewAccountEvent,
+} from "../types/events";
+
+export class NormalisedBalancesTransferEvent extends BalancesTransferEvent {
+  resolve(): ResolvedBalancesTransferEvent {
+    if (this.isWatrNodeV1000) {
+      const { from, to, amount } = this.asWatrNodeV1000;
+      return {
+        from: ss58.codec(ss58Format).encode(from),
+        to: ss58.codec(ss58Format).encode(to),
+        amount,
+      };
+    }
+    throw new Error(
+      "No runtime version found while decoding [BalancesTransferEvent]"
+    );
+  }
+}
+
+export class NormalisedBalancesEndowedEvent extends BalancesEndowedEvent {
+  resolve(): ResolvedBalancesEndowedEvent {
+    if (this.isWatrNodeV1000) {
+      const { account, freeBalance } = this.asWatrNodeV1000;
+      return {
+        account: ss58.codec(ss58Format).encode(account),
+        freeBalance,
+      };
+    }
+    throw new Error(
+      "No runtime version found while decoding [BalancesEndowedEvent]"
+    );
+  }
+}
+
+export class NormalisedBalancesWithdrawEvent extends BalancesWithdrawEvent {
+  resolve(): ResolvedBalancesWithdrawEvent {
+    if (this.isWatrNodeV1000) {
+      const { who, amount } = this.asWatrNodeV1000;
+      return {
+        account: ss58.codec(ss58Format).encode(who),
+        amount,
+      };
+    }
+    throw new Error(
+      "No runtime version found while decoding [BalancesWithdrawEvent]"
+    );
+  }
+}
+
+export class NormalisedBalancesReservedEvent extends BalancesReservedEvent {
+  resolve(): ResolvedBalancesReservedEvent {
+    if (this.isWatrNodeV1000) {
+      const { who, amount } = this.asWatrNodeV1000;
+      return {
+        account: ss58.codec(ss58Format).encode(who),
+        amount,
+      };
+    }
+    throw new Error(
+      "No runtime version found while decoding [BalancesReservedEvent]"
+    );
+  }
+}
+
+export class NormalisedContractsCodeRemovedEvent extends ContractsCodeRemovedEvent {
+  resolve(): ResolvedContractCodeRemovedEvent {
+    if (this.isWatrNodeV1000) {
+      const { codeHash } = this.asWatrNodeV1000;
+      return { codeHash: toHex(codeHash) };
+    }
+    throw new Error(
+      "No runtime version found while decoding [ContractsCodeRemovedEvent]"
+    );
+  }
+}
+
+export class NormalisedContractsInstantiatedEvent extends ContractsInstantiatedEvent {
+  resolve(): ResolvedContractsInstantiatedEvent {
+    if (this.isWatrNodeV1000) {
+      const { deployer, contract } = this.asWatrNodeV1000;
+      return {
+        deployer: ss58.codec(ss58Format).encode(deployer),
+        contract: ss58.codec(ss58Format).encode(contract),
+      };
+    }
+    throw new Error(
+      "No runtime version found while decoding [ContractsInstantiatedEvent]"
+    );
+  }
+}
+
+export class NormalisedContractsCodeStoredEvent extends ContractsCodeStoredEvent {
+  resolve(): ResolvedContractsCodeStoredEvent {
+    if (this.isWatrNodeV1000) {
+      const { codeHash } = this.asWatrNodeV1000;
+      return { codeHash: toHex(codeHash) };
+    }
+    throw new Error(
+      "No runtime version found while decoding [ContractsCodeStoredEvent]"
+    );
+  }
+}
+
+export class NormalisedContractsCodeUpdatedEvent extends ContractsContractCodeUpdatedEvent {
+  resolve(): ResolvedContractsCodeUpdatedEvent {
+    if (this.isWatrNodeV1000) {
+      const { contract, newCodeHash, oldCodeHash } = this.asWatrNodeV1000;
+      return {
+        contract: ss58.codec(ss58Format).encode(contract),
+        newCodeHash: toHex(newCodeHash),
+        oldCodeHash: toHex(oldCodeHash),
+      };
+    }
+    throw new Error(
+      "No runtime version found while decoding [ContractsContractCodeUpdatedEvent]"
+    );
+  }
+}
+
+export class NormalisedContractEmittedEvent extends ContractsContractEmittedEvent {
+  resolve(): ResolvedContractEmittedEvent {
+    if (this.isWatrNodeV1000) {
+      const { contract, data } = this.asWatrNodeV1000;
+      return { contract: ss58.codec(ss58Format).encode(contract), data };
+    }
+    throw new Error(
+      "No runtime version found while decoding [ContractsContractEmittedEvent]"
+    );
+  }
+}
+
+export class NormalisedContractTerminatedEvent extends ContractsTerminatedEvent {
+  resolve(): ResolvedContractTerminatedEvent {
+    if (this.isWatrNodeV1000) {
+      const { contract, beneficiary } = this.asWatrNodeV1000 as {
+        contract: Uint8Array;
+        beneficiary: Uint8Array;
+      };
+      return {
+        contract: ss58.codec(ss58Format).encode(contract),
+        beneficiary: ss58.codec(ss58Format).encode(beneficiary),
+      };
+    }
+    throw new Error(
+      "No runtime version found while decoding [ContractsTerminatedEvent]"
+    );
+  }
+}
+
+export class NormalisedSystemNewAccountEvent extends SystemNewAccountEvent {
+  resolve(): ResolvedNewAccountEvent {
+    if (this.isWatrNodeV1000) {
+      const { account } = this.asWatrNodeV1000;
+      return { account: ss58.codec(ss58Format).encode(account) };
+    }
+    throw new Error(
+      "No runtime version found while decoding [SystemNewAccountEvent]"
+    );
+  }
+}
diff --git a/squid-ink/src/chains/watrtestnet/normalised-types/index.ts b/squid-ink/src/chains/watrtestnet/normalised-types/index.ts
new file mode 100644
index 0000000..8eaa778
--- /dev/null
+++ b/squid-ink/src/chains/watrtestnet/normalised-types/index.ts
@@ -0,0 +1,3 @@
+export * from "./calls";
+export * from "./events";
+export * from "./storage";
diff --git a/squid-ink/src/chains/watrtestnet/normalised-types/storage.ts b/squid-ink/src/chains/watrtestnet/normalised-types/storage.ts
new file mode 100644
index 0000000..3b710c8
--- /dev/null
+++ b/squid-ink/src/chains/watrtestnet/normalised-types/storage.ts
@@ -0,0 +1,90 @@
+import assert from "assert";
+import * as ss58 from "@subsquid/ss58";
+import { decodeHex } from "@subsquid/util-internal-hex";
+import { ResolvedContractInfoOfStorage } from "chains/normalised-return-types";
+import {
+  BalancesAccountStorage,
+  ContractsCodeStorageStorage,
+  ContractsContractInfoOfStorage,
+  ContractsOwnerInfoOfStorage,
+  SystemAccountStorage,
+} from "../types/storage";
+import {
+  AccountData,
+  AccountInfo,
+  OwnerInfo,
+  PrefabWasmModule,
+} from "../types/watrNodeV1000";
+import { ss58Format } from "../../../chain-config";
+
+export class NormalisedSystemAccountStorage extends SystemAccountStorage {
+  async get(accountId: string): Promise<AccountInfo> {
+    assert(this.isExists);
+    if (this.isWatrNodeV1000) {
+      return this.getAsWatrNodeV1000(ss58.codec(ss58Format).decode(accountId));
+    }
+    throw new Error("No Runtime version found");
+  }
+}
+
+export class NormalisedBalancesAccountStorage extends BalancesAccountStorage {
+  async get(accountId: string): Promise<AccountData> {
+    assert(this.isExists);
+    if (this.isWatrNodeV1000) {
+      return this.getAsWatrNodeV1000(ss58.codec(ss58Format).decode(accountId));
+    }
+    throw new Error("No Runtime version found");
+  }
+}
+
+export class NormalisedContractInfoOfStorage extends ContractsContractInfoOfStorage {
+  async get(accountId: string): Promise<ResolvedContractInfoOfStorage> {
+    assert(this.isExists);
+    let info: ResolvedContractInfoOfStorage | undefined;
+    if (this.isWatrNodeV1000) {
+      info = await this.getAsWatrNodeV1000(
+        ss58.codec(ss58Format).decode(accountId)
+      );
+    } else {
+      throw new Error("No Runtime version found");
+    }
+    if (info) {
+      return info;
+    }
+    throw new Error(
+      `ContractInfoOf not found in storage for accountId [${accountId}]`
+    );
+  }
+}
+
+export class NormalisedCodeStorageStorage extends ContractsCodeStorageStorage {
+  async get(key: string): Promise<PrefabWasmModule> {
+    assert(this.isExists);
+    let info: PrefabWasmModule | undefined;
+    if (this.isWatrNodeV1000) {
+      info = await this.getAsWatrNodeV1000(decodeHex(key));
+    } else {
+      throw new Error("No Runtime version found");
+    }
+    if (info) {
+      return info;
+    }
+    throw new Error(`CodeStorage not found in storage for key [${key}]`);
+  }
+}
+
+export class NormalisedOwnerInfoOfStorage extends ContractsOwnerInfoOfStorage {
+  async get(key: string): Promise<OwnerInfo> {
+    assert(this.isExists);
+    let info: OwnerInfo | undefined;
+    if (this.isWatrNodeV1000) {
+      info = await this.getAsWatrNodeV1000(decodeHex(key));
+    } else {
+      throw new Error("No Runtime version found");
+    }
+    if (info) {
+      return info;
+    }
+    throw new Error(`CodeStorage not found in storage for key [${key}]`);
+  }
+}
diff --git a/squid-ink/src/chains/watrtestnet/types/calls.ts b/squid-ink/src/chains/watrtestnet/types/calls.ts
new file mode 100644
index 0000000..ee7daf5
--- /dev/null
+++ b/squid-ink/src/chains/watrtestnet/types/calls.ts
@@ -0,0 +1,62 @@
+import assert from 'assert'
+import {Chain, ChainContext, CallContext, Call, Result, Option} from './support'
+import * as watrNodeV1000 from './watrNodeV1000'
+
+export class ContractsCallCall {
+    private readonly _chain: Chain
+    private readonly call: Call
+
+    constructor(ctx: CallContext)
+    constructor(ctx: ChainContext, call: Call)
+    constructor(ctx: CallContext, call?: Call) {
+        call = call || ctx.call
+        assert(call.name === 'Contracts.call')
+        this._chain = ctx._chain
+        this.call = call
+    }
+
+    /**
+     * Makes a call to an account, optionally transferring some balance.
+     * 
+     * # Parameters
+     * 
+     * * `dest`: Address of the contract to call.
+     * * `value`: The balance to transfer from the `origin` to `dest`.
+     * * `gas_limit`: The gas limit enforced when executing the constructor.
+     * * `storage_deposit_limit`: The maximum amount of balance that can be charged from the
+     *   caller to pay for the storage consumed.
+     * * `data`: The input data to pass to the contract.
+     * 
+     * * If the account is a smart-contract account, the associated code will be
+     * executed and any value will be transferred.
+     * * If the account is a regular account, any value will be transferred.
+     * * If no account exists and the call value is not less than `existential_deposit`,
+     * a regular account will be created and any value will be transferred.
+     */
+    get isWatrNodeV1000(): boolean {
+        return this._chain.getCallHash('Contracts.call') === 'd96c8a6656d7a4d6af6d5d0d51dd36e041c9ea8a92a7ead343d711addd74780f'
+    }
+
+    /**
+     * Makes a call to an account, optionally transferring some balance.
+     * 
+     * # Parameters
+     * 
+     * * `dest`: Address of the contract to call.
+     * * `value`: The balance to transfer from the `origin` to `dest`.
+     * * `gas_limit`: The gas limit enforced when executing the constructor.
+     * * `storage_deposit_limit`: The maximum amount of balance that can be charged from the
+     *   caller to pay for the storage consumed.
+     * * `data`: The input data to pass to the contract.
+     * 
+     * * If the account is a smart-contract account, the associated code will be
+     * executed and any value will be transferred.
+     * * If the account is a regular account, any value will be transferred.
+     * * If no account exists and the call value is not less than `existential_deposit`,
+     * a regular account will be created and any value will be transferred.
+     */
+    get asWatrNodeV1000(): {dest: watrNodeV1000.MultiAddress, value: bigint, gasLimit: bigint, storageDepositLimit: (bigint | undefined), data: Uint8Array} {
+        assert(this.isWatrNodeV1000)
+        return this._chain.decodeCall(this.call)
+    }
+}
diff --git a/squid-ink/src/chains/watrtestnet/types/events.ts b/squid-ink/src/chains/watrtestnet/types/events.ts
new file mode 100644
index 0000000..a90eda7
--- /dev/null
+++ b/squid-ink/src/chains/watrtestnet/types/events.ts
@@ -0,0 +1,331 @@
+import assert from 'assert'
+import {Chain, ChainContext, EventContext, Event, Result, Option} from './support'
+
+export class BalancesEndowedEvent {
+    private readonly _chain: Chain
+    private readonly event: Event
+
+    constructor(ctx: EventContext)
+    constructor(ctx: ChainContext, event: Event)
+    constructor(ctx: EventContext, event?: Event) {
+        event = event || ctx.event
+        assert(event.name === 'Balances.Endowed')
+        this._chain = ctx._chain
+        this.event = event
+    }
+
+    /**
+     * An account was created with some free balance.
+     */
+    get isWatrNodeV1000(): boolean {
+        return this._chain.getEventHash('Balances.Endowed') === '75951f685df19cbb5fdda09cf928a105518ceca9576d95bd18d4fac8802730ca'
+    }
+
+    /**
+     * An account was created with some free balance.
+     */
+    get asWatrNodeV1000(): {account: Uint8Array, freeBalance: bigint} {
+        assert(this.isWatrNodeV1000)
+        return this._chain.decodeEvent(this.event)
+    }
+}
+
+export class BalancesReservedEvent {
+    private readonly _chain: Chain
+    private readonly event: Event
+
+    constructor(ctx: EventContext)
+    constructor(ctx: ChainContext, event: Event)
+    constructor(ctx: EventContext, event?: Event) {
+        event = event || ctx.event
+        assert(event.name === 'Balances.Reserved')
+        this._chain = ctx._chain
+        this.event = event
+    }
+
+    /**
+     * Some balance was reserved (moved from free to reserved).
+     */
+    get isWatrNodeV1000(): boolean {
+        return this._chain.getEventHash('Balances.Reserved') === 'e84a34a6a3d577b31f16557bd304282f4fe4cbd7115377f4687635dc48e52ba5'
+    }
+
+    /**
+     * Some balance was reserved (moved from free to reserved).
+     */
+    get asWatrNodeV1000(): {who: Uint8Array, amount: bigint} {
+        assert(this.isWatrNodeV1000)
+        return this._chain.decodeEvent(this.event)
+    }
+}
+
+export class BalancesTransferEvent {
+    private readonly _chain: Chain
+    private readonly event: Event
+
+    constructor(ctx: EventContext)
+    constructor(ctx: ChainContext, event: Event)
+    constructor(ctx: EventContext, event?: Event) {
+        event = event || ctx.event
+        assert(event.name === 'Balances.Transfer')
+        this._chain = ctx._chain
+        this.event = event
+    }
+
+    /**
+     * Transfer succeeded.
+     */
+    get isWatrNodeV1000(): boolean {
+        return this._chain.getEventHash('Balances.Transfer') === '0ffdf35c495114c2d42a8bf6c241483fd5334ca0198662e14480ad040f1e3a66'
+    }
+
+    /**
+     * Transfer succeeded.
+     */
+    get asWatrNodeV1000(): {from: Uint8Array, to: Uint8Array, amount: bigint} {
+        assert(this.isWatrNodeV1000)
+        return this._chain.decodeEvent(this.event)
+    }
+}
+
+export class BalancesWithdrawEvent {
+    private readonly _chain: Chain
+    private readonly event: Event
+
+    constructor(ctx: EventContext)
+    constructor(ctx: ChainContext, event: Event)
+    constructor(ctx: EventContext, event?: Event) {
+        event = event || ctx.event
+        assert(event.name === 'Balances.Withdraw')
+        this._chain = ctx._chain
+        this.event = event
+    }
+
+    /**
+     * Some amount was withdrawn from the account (e.g. for transaction fees).
+     */
+    get isWatrNodeV1000(): boolean {
+        return this._chain.getEventHash('Balances.Withdraw') === 'e84a34a6a3d577b31f16557bd304282f4fe4cbd7115377f4687635dc48e52ba5'
+    }
+
+    /**
+     * Some amount was withdrawn from the account (e.g. for transaction fees).
+     */
+    get asWatrNodeV1000(): {who: Uint8Array, amount: bigint} {
+        assert(this.isWatrNodeV1000)
+        return this._chain.decodeEvent(this.event)
+    }
+}
+
+export class ContractsCodeRemovedEvent {
+    private readonly _chain: Chain
+    private readonly event: Event
+
+    constructor(ctx: EventContext)
+    constructor(ctx: ChainContext, event: Event)
+    constructor(ctx: EventContext, event?: Event) {
+        event = event || ctx.event
+        assert(event.name === 'Contracts.CodeRemoved')
+        this._chain = ctx._chain
+        this.event = event
+    }
+
+    /**
+     * A code with the specified hash was removed.
+     */
+    get isWatrNodeV1000(): boolean {
+        return this._chain.getEventHash('Contracts.CodeRemoved') === '9e5c86c297bd88fae31bc40119e44695818ddc3ab8842b90daeb12771005c70d'
+    }
+
+    /**
+     * A code with the specified hash was removed.
+     */
+    get asWatrNodeV1000(): {codeHash: Uint8Array} {
+        assert(this.isWatrNodeV1000)
+        return this._chain.decodeEvent(this.event)
+    }
+}
+
+export class ContractsCodeStoredEvent {
+    private readonly _chain: Chain
+    private readonly event: Event
+
+    constructor(ctx: EventContext)
+    constructor(ctx: ChainContext, event: Event)
+    constructor(ctx: EventContext, event?: Event) {
+        event = event || ctx.event
+        assert(event.name === 'Contracts.CodeStored')
+        this._chain = ctx._chain
+        this.event = event
+    }
+
+    /**
+     * Code with the specified hash has been stored.
+     */
+    get isWatrNodeV1000(): boolean {
+        return this._chain.getEventHash('Contracts.CodeStored') === '9e5c86c297bd88fae31bc40119e44695818ddc3ab8842b90daeb12771005c70d'
+    }
+
+    /**
+     * Code with the specified hash has been stored.
+     */
+    get asWatrNodeV1000(): {codeHash: Uint8Array} {
+        assert(this.isWatrNodeV1000)
+        return this._chain.decodeEvent(this.event)
+    }
+}
+
+export class ContractsContractCodeUpdatedEvent {
+    private readonly _chain: Chain
+    private readonly event: Event
+
+    constructor(ctx: EventContext)
+    constructor(ctx: ChainContext, event: Event)
+    constructor(ctx: EventContext, event?: Event) {
+        event = event || ctx.event
+        assert(event.name === 'Contracts.ContractCodeUpdated')
+        this._chain = ctx._chain
+        this.event = event
+    }
+
+    /**
+     * A contract's code was updated.
+     */
+    get isWatrNodeV1000(): boolean {
+        return this._chain.getEventHash('Contracts.ContractCodeUpdated') === 'f9de6decda4961d31d7cf59e3f8acd4849a220323ebabbb036464d999de54c18'
+    }
+
+    /**
+     * A contract's code was updated.
+     */
+    get asWatrNodeV1000(): {contract: Uint8Array, newCodeHash: Uint8Array, oldCodeHash: Uint8Array} {
+        assert(this.isWatrNodeV1000)
+        return this._chain.decodeEvent(this.event)
+    }
+}
+
+export class ContractsContractEmittedEvent {
+    private readonly _chain: Chain
+    private readonly event: Event
+
+    constructor(ctx: EventContext)
+    constructor(ctx: ChainContext, event: Event)
+    constructor(ctx: EventContext, event?: Event) {
+        event = event || ctx.event
+        assert(event.name === 'Contracts.ContractEmitted')
+        this._chain = ctx._chain
+        this.event = event
+    }
+
+    /**
+     * A custom event emitted by the contract.
+     */
+    get isWatrNodeV1000(): boolean {
+        return this._chain.getEventHash('Contracts.ContractEmitted') === '7f28393268795b9a97f05e82911cdcc4200d99e9968c1ab6a564f949f753b929'
+    }
+
+    /**
+     * A custom event emitted by the contract.
+     */
+    get asWatrNodeV1000(): {contract: Uint8Array, data: Uint8Array} {
+        assert(this.isWatrNodeV1000)
+        return this._chain.decodeEvent(this.event)
+    }
+}
+
+export class ContractsInstantiatedEvent {
+    private readonly _chain: Chain
+    private readonly event: Event
+
+    constructor(ctx: EventContext)
+    constructor(ctx: ChainContext, event: Event)
+    constructor(ctx: EventContext, event?: Event) {
+        event = event || ctx.event
+        assert(event.name === 'Contracts.Instantiated')
+        this._chain = ctx._chain
+        this.event = event
+    }
+
+    /**
+     * Contract deployed by address at the specified address.
+     */
+    get isWatrNodeV1000(): boolean {
+        return this._chain.getEventHash('Contracts.Instantiated') === '20f9f9057a4149f58eb48c00359f9800a42b51d4d2168437dfcce668c27a8d37'
+    }
+
+    /**
+     * Contract deployed by address at the specified address.
+     */
+    get asWatrNodeV1000(): {deployer: Uint8Array, contract: Uint8Array} {
+        assert(this.isWatrNodeV1000)
+        return this._chain.decodeEvent(this.event)
+    }
+}
+
+export class ContractsTerminatedEvent {
+    private readonly _chain: Chain
+    private readonly event: Event
+
+    constructor(ctx: EventContext)
+    constructor(ctx: ChainContext, event: Event)
+    constructor(ctx: EventContext, event?: Event) {
+        event = event || ctx.event
+        assert(event.name === 'Contracts.Terminated')
+        this._chain = ctx._chain
+        this.event = event
+    }
+
+    /**
+     * Contract has been removed.
+     * 
+     * # Note
+     * 
+     * The only way for a contract to be removed and emitting this event is by calling
+     * `seal_terminate`.
+     */
+    get isWatrNodeV1000(): boolean {
+        return this._chain.getEventHash('Contracts.Terminated') === '8e0b376b4821223ecd835a0ae76a615e7aa14158260ff9c7f87220449d98175b'
+    }
+
+    /**
+     * Contract has been removed.
+     * 
+     * # Note
+     * 
+     * The only way for a contract to be removed and emitting this event is by calling
+     * `seal_terminate`.
+     */
+    get asWatrNodeV1000(): {contract: Uint8Array, beneficiary: Uint8Array} {
+        assert(this.isWatrNodeV1000)
+        return this._chain.decodeEvent(this.event)
+    }
+}
+
+export class SystemNewAccountEvent {
+    private readonly _chain: Chain
+    private readonly event: Event
+
+    constructor(ctx: EventContext)
+    constructor(ctx: ChainContext, event: Event)
+    constructor(ctx: EventContext, event?: Event) {
+        event = event || ctx.event
+        assert(event.name === 'System.NewAccount')
+        this._chain = ctx._chain
+        this.event = event
+    }
+
+    /**
+     * A new account was created.
+     */
+    get isWatrNodeV1000(): boolean {
+        return this._chain.getEventHash('System.NewAccount') === '7fb7672b764b0a4f0c4910fddefec0709628843df7ad0073a97eede13c53ca92'
+    }
+
+    /**
+     * A new account was created.
+     */
+    get asWatrNodeV1000(): {account: Uint8Array} {
+        assert(this.isWatrNodeV1000)
+        return this._chain.decodeEvent(this.event)
+    }
+}
diff --git a/squid-ink/src/chains/watrtestnet/types/storage.ts b/squid-ink/src/chains/watrtestnet/types/storage.ts
new file mode 100644
index 0000000..98542b3
--- /dev/null
+++ b/squid-ink/src/chains/watrtestnet/types/storage.ts
@@ -0,0 +1,278 @@
+import assert from 'assert'
+import {Block, Chain, ChainContext, BlockContext, Result, Option} from './support'
+import * as watrNodeV1000 from './watrNodeV1000'
+
+export class BalancesAccountStorage {
+    private readonly _chain: Chain
+    private readonly blockHash: string
+
+    constructor(ctx: BlockContext)
+    constructor(ctx: ChainContext, block: Block)
+    constructor(ctx: BlockContext, block?: Block) {
+        block = block || ctx.block
+        this.blockHash = block.hash
+        this._chain = ctx._chain
+    }
+
+    /**
+     *  The Balances pallet example of storing the balance of an account.
+     * 
+     *  # Example
+     * 
+     *  ```rust
+     *   impl pallet_balances::Config for Runtime {
+     *     type AccountStore = StorageMapShim<Self::Account<Runtime>, frame_system::Provider<Runtime>, AccountId, Self::AccountData<Balance>>
+     *   }
+     *  ```
+     * 
+     *  You can also store the balance of an account in the `System` pallet.
+     * 
+     *  # Example
+     * 
+     *  ```rust
+     *   impl pallet_balances::Config for Runtime {
+     *    type AccountStore = System
+     *   }
+     *  ```
+     * 
+     *  But this comes with tradeoffs, storing account balances in the system pallet stores
+     *  `frame_system` data alongside the account data contrary to storing account balances in the
+     *  `Balances` pallet, which uses a `StorageMap` to store balances data only.
+     *  NOTE: This is only used in the case that this pallet is used to store balances.
+     */
+    get isWatrNodeV1000() {
+        return this._chain.getStorageItemTypeHash('Balances', 'Account') === '0b3b4bf0dd7388459eba461bc7c3226bf58608c941710a714e02f33ec0f91e78'
+    }
+
+    /**
+     *  The Balances pallet example of storing the balance of an account.
+     * 
+     *  # Example
+     * 
+     *  ```rust
+     *   impl pallet_balances::Config for Runtime {
+     *     type AccountStore = StorageMapShim<Self::Account<Runtime>, frame_system::Provider<Runtime>, AccountId, Self::AccountData<Balance>>
+     *   }
+     *  ```
+     * 
+     *  You can also store the balance of an account in the `System` pallet.
+     * 
+     *  # Example
+     * 
+     *  ```rust
+     *   impl pallet_balances::Config for Runtime {
+     *    type AccountStore = System
+     *   }
+     *  ```
+     * 
+     *  But this comes with tradeoffs, storing account balances in the system pallet stores
+     *  `frame_system` data alongside the account data contrary to storing account balances in the
+     *  `Balances` pallet, which uses a `StorageMap` to store balances data only.
+     *  NOTE: This is only used in the case that this pallet is used to store balances.
+     */
+    async getAsWatrNodeV1000(key: Uint8Array): Promise<watrNodeV1000.AccountData> {
+        assert(this.isWatrNodeV1000)
+        return this._chain.getStorage(this.blockHash, 'Balances', 'Account', key)
+    }
+
+    async getManyAsWatrNodeV1000(keys: Uint8Array[]): Promise<(watrNodeV1000.AccountData)[]> {
+        assert(this.isWatrNodeV1000)
+        return this._chain.queryStorage(this.blockHash, 'Balances', 'Account', keys.map(k => [k]))
+    }
+
+    async getAllAsWatrNodeV1000(): Promise<(watrNodeV1000.AccountData)[]> {
+        assert(this.isWatrNodeV1000)
+        return this._chain.queryStorage(this.blockHash, 'Balances', 'Account')
+    }
+
+    /**
+     * Checks whether the storage item is defined for the current chain version.
+     */
+    get isExists(): boolean {
+        return this._chain.getStorageItemTypeHash('Balances', 'Account') != null
+    }
+}
+
+export class ContractsCodeStorageStorage {
+    private readonly _chain: Chain
+    private readonly blockHash: string
+
+    constructor(ctx: BlockContext)
+    constructor(ctx: ChainContext, block: Block)
+    constructor(ctx: BlockContext, block?: Block) {
+        block = block || ctx.block
+        this.blockHash = block.hash
+        this._chain = ctx._chain
+    }
+
+    /**
+     *  A mapping between an original code hash and instrumented wasm code, ready for execution.
+     */
+    get isWatrNodeV1000() {
+        return this._chain.getStorageItemTypeHash('Contracts', 'CodeStorage') === '1d41f869264eec7411828c1a845cdbad1a39455691f254f6bfead6b3102145ab'
+    }
+
+    /**
+     *  A mapping between an original code hash and instrumented wasm code, ready for execution.
+     */
+    async getAsWatrNodeV1000(key: Uint8Array): Promise<watrNodeV1000.PrefabWasmModule | undefined> {
+        assert(this.isWatrNodeV1000)
+        return this._chain.getStorage(this.blockHash, 'Contracts', 'CodeStorage', key)
+    }
+
+    async getManyAsWatrNodeV1000(keys: Uint8Array[]): Promise<(watrNodeV1000.PrefabWasmModule | undefined)[]> {
+        assert(this.isWatrNodeV1000)
+        return this._chain.queryStorage(this.blockHash, 'Contracts', 'CodeStorage', keys.map(k => [k]))
+    }
+
+    async getAllAsWatrNodeV1000(): Promise<(watrNodeV1000.PrefabWasmModule)[]> {
+        assert(this.isWatrNodeV1000)
+        return this._chain.queryStorage(this.blockHash, 'Contracts', 'CodeStorage')
+    }
+
+    /**
+     * Checks whether the storage item is defined for the current chain version.
+     */
+    get isExists(): boolean {
+        return this._chain.getStorageItemTypeHash('Contracts', 'CodeStorage') != null
+    }
+}
+
+export class ContractsContractInfoOfStorage {
+    private readonly _chain: Chain
+    private readonly blockHash: string
+
+    constructor(ctx: BlockContext)
+    constructor(ctx: ChainContext, block: Block)
+    constructor(ctx: BlockContext, block?: Block) {
+        block = block || ctx.block
+        this.blockHash = block.hash
+        this._chain = ctx._chain
+    }
+
+    /**
+     *  The code associated with a given account.
+     * 
+     *  TWOX-NOTE: SAFE since `AccountId` is a secure hash.
+     */
+    get isWatrNodeV1000() {
+        return this._chain.getStorageItemTypeHash('Contracts', 'ContractInfoOf') === 'ca1ad2ae4b550883411d45c2158af4f3e2a0bde306e44674a586527ce222bcf3'
+    }
+
+    /**
+     *  The code associated with a given account.
+     * 
+     *  TWOX-NOTE: SAFE since `AccountId` is a secure hash.
+     */
+    async getAsWatrNodeV1000(key: Uint8Array): Promise<watrNodeV1000.RawContractInfo | undefined> {
+        assert(this.isWatrNodeV1000)
+        return this._chain.getStorage(this.blockHash, 'Contracts', 'ContractInfoOf', key)
+    }
+
+    async getManyAsWatrNodeV1000(keys: Uint8Array[]): Promise<(watrNodeV1000.RawContractInfo | undefined)[]> {
+        assert(this.isWatrNodeV1000)
+        return this._chain.queryStorage(this.blockHash, 'Contracts', 'ContractInfoOf', keys.map(k => [k]))
+    }
+
+    async getAllAsWatrNodeV1000(): Promise<(watrNodeV1000.RawContractInfo)[]> {
+        assert(this.isWatrNodeV1000)
+        return this._chain.queryStorage(this.blockHash, 'Contracts', 'ContractInfoOf')
+    }
+
+    /**
+     * Checks whether the storage item is defined for the current chain version.
+     */
+    get isExists(): boolean {
+        return this._chain.getStorageItemTypeHash('Contracts', 'ContractInfoOf') != null
+    }
+}
+
+export class ContractsOwnerInfoOfStorage {
+    private readonly _chain: Chain
+    private readonly blockHash: string
+
+    constructor(ctx: BlockContext)
+    constructor(ctx: ChainContext, block: Block)
+    constructor(ctx: BlockContext, block?: Block) {
+        block = block || ctx.block
+        this.blockHash = block.hash
+        this._chain = ctx._chain
+    }
+
+    /**
+     *  A mapping between an original code hash and its owner information.
+     */
+    get isWatrNodeV1000() {
+        return this._chain.getStorageItemTypeHash('Contracts', 'OwnerInfoOf') === '76689686c73821ee740f33d092a38a05de83a2833f6c8857baa886203c5bf939'
+    }
+
+    /**
+     *  A mapping between an original code hash and its owner information.
+     */
+    async getAsWatrNodeV1000(key: Uint8Array): Promise<watrNodeV1000.OwnerInfo | undefined> {
+        assert(this.isWatrNodeV1000)
+        return this._chain.getStorage(this.blockHash, 'Contracts', 'OwnerInfoOf', key)
+    }
+
+    async getManyAsWatrNodeV1000(keys: Uint8Array[]): Promise<(watrNodeV1000.OwnerInfo | undefined)[]> {
+        assert(this.isWatrNodeV1000)
+        return this._chain.queryStorage(this.blockHash, 'Contracts', 'OwnerInfoOf', keys.map(k => [k]))
+    }
+
+    async getAllAsWatrNodeV1000(): Promise<(watrNodeV1000.OwnerInfo)[]> {
+        assert(this.isWatrNodeV1000)
+        return this._chain.queryStorage(this.blockHash, 'Contracts', 'OwnerInfoOf')
+    }
+
+    /**
+     * Checks whether the storage item is defined for the current chain version.
+     */
+    get isExists(): boolean {
+        return this._chain.getStorageItemTypeHash('Contracts', 'OwnerInfoOf') != null
+    }
+}
+
+export class SystemAccountStorage {
+    private readonly _chain: Chain
+    private readonly blockHash: string
+
+    constructor(ctx: BlockContext)
+    constructor(ctx: ChainContext, block: Block)
+    constructor(ctx: BlockContext, block?: Block) {
+        block = block || ctx.block
+        this.blockHash = block.hash
+        this._chain = ctx._chain
+    }
+
+    /**
+     *  The full account information for a particular account ID.
+     */
+    get isWatrNodeV1000() {
+        return this._chain.getStorageItemTypeHash('System', 'Account') === '1ddc7ade926221442c388ee4405a71c9428e548fab037445aaf4b3a78f4735c1'
+    }
+
+    /**
+     *  The full account information for a particular account ID.
+     */
+    async getAsWatrNodeV1000(key: Uint8Array): Promise<watrNodeV1000.AccountInfo> {
+        assert(this.isWatrNodeV1000)
+        return this._chain.getStorage(this.blockHash, 'System', 'Account', key)
+    }
+
+    async getManyAsWatrNodeV1000(keys: Uint8Array[]): Promise<(watrNodeV1000.AccountInfo)[]> {
+        assert(this.isWatrNodeV1000)
+        return this._chain.queryStorage(this.blockHash, 'System', 'Account', keys.map(k => [k]))
+    }
+
+    async getAllAsWatrNodeV1000(): Promise<(watrNodeV1000.AccountInfo)[]> {
+        assert(this.isWatrNodeV1000)
+        return this._chain.queryStorage(this.blockHash, 'System', 'Account')
+    }
+
+    /**
+     * Checks whether the storage item is defined for the current chain version.
+     */
+    get isExists(): boolean {
+        return this._chain.getStorageItemTypeHash('System', 'Account') != null
+    }
+}
diff --git a/squid-ink/src/chains/watrtestnet/types/support.ts b/squid-ink/src/chains/watrtestnet/types/support.ts
new file mode 100644
index 0000000..6d150b2
--- /dev/null
+++ b/squid-ink/src/chains/watrtestnet/types/support.ts
@@ -0,0 +1,66 @@
+
+export type Result<T, E> = {
+    __kind: 'Ok'
+    value: T
+} | {
+    __kind: 'Err'
+    value: E
+}
+
+
+export type Option<T> = {
+    __kind: 'Some',
+    value: T
+} | {
+    __kind: 'None'
+}
+
+
+export interface Chain {
+    getEventHash(eventName: string): string
+    decodeEvent(event: Event): any
+    getCallHash(name: string): string
+    decodeCall(call: Call): any
+    getStorageItemTypeHash(prefix: string, name: string): string | undefined
+    getStorage(blockHash: string, prefix: string, name: string, ...args: any[]): Promise<any>
+    queryStorage(blockHash: string, prefix: string, name: string, ...args: any[]): Promise<any[]>
+    getConstantTypeHash(pallet: string, name: string): string | undefined
+    getConstant(pallet: string, name: string): any
+}
+
+
+export interface ChainContext {
+    _chain: Chain
+}
+
+
+export interface Event {
+    name: string
+    args: any
+}
+
+
+export interface EventContext extends ChainContext {
+    event: Event
+}
+
+
+export interface Call {
+    name: string
+    args: any
+}
+
+
+export interface CallContext extends ChainContext {
+    call: Call
+}
+
+
+export interface BlockContext extends ChainContext {
+    block: Block
+}
+
+
+export interface Block {
+    hash: string
+}
diff --git a/squid-ink/src/chains/watrtestnet/types/watrNodeV1000.ts b/squid-ink/src/chains/watrtestnet/types/watrNodeV1000.ts
new file mode 100644
index 0000000..3b53893
--- /dev/null
+++ b/squid-ink/src/chains/watrtestnet/types/watrNodeV1000.ts
@@ -0,0 +1,62 @@
+import type {Result, Option} from './support'
+
+export type MultiAddress = MultiAddress_Id | MultiAddress_Index | MultiAddress_Raw | MultiAddress_Address32 | MultiAddress_Address20
+
+export interface MultiAddress_Id {
+    __kind: 'Id'
+    value: Uint8Array
+}
+
+export interface MultiAddress_Index {
+    __kind: 'Index'
+    value: null
+}
+
+export interface MultiAddress_Raw {
+    __kind: 'Raw'
+    value: Uint8Array
+}
+
+export interface MultiAddress_Address32 {
+    __kind: 'Address32'
+    value: Uint8Array
+}
+
+export interface MultiAddress_Address20 {
+    __kind: 'Address20'
+    value: Uint8Array
+}
+
+export interface AccountData {
+    free: bigint
+    reserved: bigint
+    miscFrozen: bigint
+    feeFrozen: bigint
+}
+
+export interface PrefabWasmModule {
+    instructionWeightsVersion: number
+    initial: number
+    maximum: number
+    code: Uint8Array
+}
+
+export interface RawContractInfo {
+    trieId: Uint8Array
+    codeHash: Uint8Array
+    storageDeposit: bigint
+}
+
+export interface OwnerInfo {
+    owner: Uint8Array
+    deposit: bigint
+    refcount: bigint
+}
+
+export interface AccountInfo {
+    nonce: number
+    consumers: number
+    providers: number
+    sufficients: number
+    data: AccountData
+}