From 029e1c18ea2b87c9ddd9acf99bacb2d6db0f3b53 Mon Sep 17 00:00:00 2001
From: Serban Iorga <serban@parity.io>
Date: Mon, 25 Mar 2024 08:50:57 +0100
Subject: [PATCH] Backport changes from `polkadot-sdk/master` (#2887)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* Add two new zombienet tests for bridges (manual run) (#3072)

extracted useful code from #2982

This PR:
- adds test 2 for Rococo <> Westend bridge: checks that relayer doesn't
submit any extra headers while there are no any messages;
- adds test 3 for Rococo <> Westend bridge: checks that relayer doesn't
submit any extra headers when there are messages;
- fixes most of comments from #2439 (like: log names, ability to run
specify test number when calling `run-tests.sh`).

Right now of all our tests, only test 2 is working (until BHs will be
upgraded to use async backing), so you can test it with
`./bridges/zombienet/run-tests.sh --test 2` locally.

(cherry picked from commit 2e6067d768a84e780258aa4580116f7180e24290)

* [cumulus] Improved check for sane bridge fees calculations (#3175)

- [x] change constants when CI fails (should fail :) )

On the AssetHubRococo: 1701175800126 -> 1700929825257 = 0.15 %
decreased.
```
Feb 02 12:59:05.520 ERROR bridges::estimate: `bridging::XcmBridgeHubRouterBaseFee` actual value: 1701175800126 for runtime: statemine-1006000 (statemine-0.tx14.au1)

Feb 02 13:02:40.647 ERROR bridges::estimate: `bridging::XcmBridgeHubRouterBaseFee` actual value: 1700929825257 for runtime: statemine-1006000 (statemine-0.tx14.au1)

```

On the AssetHubWestend: 2116038876326 -> 1641718372993 = 22.4 %
decreased.
```
Feb 02 12:56:00.880 ERROR bridges::estimate: `bridging::XcmBridgeHubRouterBaseFee` actual value: 2116038876326 for runtime: westmint-1006000 (westmint-0.tx14.au1)

Feb 02 13:04:42.515 ERROR bridges::estimate: `bridging::XcmBridgeHubRouterBaseFee` actual value: 1641718372993 for runtime: westmint-1006000 (westmint-0.tx14.au1)
```

(cherry picked from commit 74b597fcaf143d8dd7f8d40e59f51065514f21d7)

* Enable async backing on all testnet system chains (#2949)

Built on top of https://github.com/paritytech/polkadot-sdk/pull/2826/
which was a trial run.

Guide:
https://github.com/w3f/polkadot-wiki/blob/master/docs/maintain/maintain-guides-async-backing.md

---------

Signed-off-by: georgepisaltu <george.pisaltu@parity.io>
Co-authored-by: Branislav Kontur <bkontur@gmail.com>
Co-authored-by: Dónal Murray <donal.murray@parity.io>
Co-authored-by: Dmitry Sinyavin <dmitry.sinyavin@parity.io>
Co-authored-by: s0me0ne-unkn0wn <48632512+s0me0ne-unkn0wn@users.noreply.github.com>
Co-authored-by: Svyatoslav Nikolsky <svyatonik@gmail.com>
Co-authored-by: Bastian Köcher <info@kchr.de>
Co-authored-by: georgepisaltu <52418509+georgepisaltu@users.noreply.github.com>
(cherry picked from commit 700d5f85b768fe1867660938aa5edfcf4b26f632)

* Introduce submit_finality_proof_ex call to bridges GRANDPA pallet (#3225)

backport of
https://github.com/paritytech/parity-bridges-common/pull/2821 (see
detailed description there)

(cherry picked from commit a462207158360b162228d9877fed7b9ca1f23fc2)

* Bridge zombienet tests refactoring (#3260)

Related to https://github.com/paritytech/polkadot-sdk/issues/3242

Reorganizing the bridge zombienet tests in order to:
- separate the environment spawning from the actual tests
- offer better control over the tests and some possibility to
orchestrate them as opposed to running everything from the zndsl file

Only rewrote the asset transfer test using this new "framework". The old
logic and old tests weren't functionally modified or deleted. The plan
is to get feedback on this approach first and if this is agreed upon,
migrate the other 2 tests later in separate PRs and also do other
improvements later.

(cherry picked from commit dfc8e4696c6edfb76ccb05f469a221ebb5b270ff)

* Bridges: add test 0002 to CI (#3310)

Bridges: add test 0002 to CI
(cherry picked from commit 1b66bb51b52d3e6cacf155bd3e038b6ef44ac5da)

* Bridge zombienet tests - move all test scripts to the same folder (#3333)

Related to https://github.com/paritytech/polkadot-sdk/issues/3242

(cherry picked from commit 5fc7622cb312f2d32ec8365012ee0a49622db8c8)

* Lift dependencies to the workspace (Part 2/x) (#3366)

Lifting some more dependencies to the workspace. Just using the
most-often updated ones for now.
It can be reproduced locally.

```sh
$ zepter transpose dependency lift-to-workspace --ignore-errors syn quote thiserror "regex:^serde.*"

$ zepter transpose dependency lift-to-workspace --version-resolver=highest syn quote thiserror "regex:^serde.*" --fix

$ taplo format --config .config/taplo.toml
```

---------

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
(cherry picked from commit e89d0fca351de0712f104c55fe45ed124b5c6968)

* Add support for BHP local and BHK local (#3443)

Related to https://github.com/paritytech/polkadot-sdk/issues/3400

Extracting small parts of
https://github.com/paritytech/polkadot-sdk/pull/3429 into separate PR:

- Add support for BHP local and BHK local
- Increase the timeout for the bridge zomienet tests

(cherry picked from commit e4b6b8cd7973633f86d1b92a56abf2a946b7be84)

* Bridge zombienet tests: move all "framework" files under one folder (#3462)

Related to https://github.com/paritytech/polkadot-sdk/issues/3400

Moving all bridges testing "framework" files under one folder in order
to be able to download the entire folder when we want to add tests in
other repos

No significant functional changes

(cherry picked from commit 6fc1d41d4487b9164451cd8214674ce195ab06a0)

* Bridge zombienet tests: Check amount received at destination (#3490)

Related to https://github.com/paritytech/polkadot-sdk/issues/3475

(cherry picked from commit 2cdda0e62dd3088d2fd09cea627059674070c277)

* FRAME: Create `TransactionExtension` as a replacement for `SignedExtension` (#2280)

Closes #2160

First part of [Extrinsic
Horizon](https://github.com/paritytech/polkadot-sdk/issues/2415)

Introduces a new trait `TransactionExtension` to replace
`SignedExtension`. Introduce the idea of transactions which obey the
runtime's extensions and have according Extension data (né Extra data)
yet do not have hard-coded signatures.

Deprecate the terminology of "Unsigned" when used for
transactions/extrinsics owing to there now being "proper" unsigned
transactions which obey the extension framework and "old-style" unsigned
which do not. Instead we have __*General*__ for the former and
__*Bare*__ for the latter. (Ultimately, the latter will be phased out as
a type of transaction, and Bare will only be used for Inherents.)

Types of extrinsic are now therefore:
- Bare (no hardcoded signature, no Extra data; used to be known as
"Unsigned")
- Bare transactions (deprecated): Gossiped, validated with
`ValidateUnsigned` (deprecated) and the `_bare_compat` bits of
`TransactionExtension` (deprecated).
  - Inherents: Not gossiped, validated with `ProvideInherent`.
- Extended (Extra data): Gossiped, validated via `TransactionExtension`.
  - Signed transactions (with a hardcoded signature).
  - General transactions (without a hardcoded signature).

`TransactionExtension` differs from `SignedExtension` because:
- A signature on the underlying transaction may validly not be present.
- It may alter the origin during validation.
- `pre_dispatch` is renamed to `prepare` and need not contain the checks
present in `validate`.
- `validate` and `prepare` is passed an `Origin` rather than a
`AccountId`.
- `validate` may pass arbitrary information into `prepare` via a new
user-specifiable type `Val`.
- `AdditionalSigned`/`additional_signed` is renamed to
`Implicit`/`implicit`. It is encoded *for the entire transaction* and
passed in to each extension as a new argument to `validate`. This
facilitates the ability of extensions to acts as underlying crypto.

There is a new `DispatchTransaction` trait which contains only default
function impls and is impl'ed for any `TransactionExtension` impler. It
provides several utility functions which reduce some of the tedium from
using `TransactionExtension` (indeed, none of its regular functions
should now need to be called directly).

Three transaction version discriminator ("versions") are now
permissible:
- 0b000000100: Bare (used to be called "Unsigned"): contains Signature
or Extra (extension data). After bare transactions are no longer
supported, this will strictly identify an Inherents only.
- 0b100000100: Old-school "Signed" Transaction: contains Signature and
Extra (extension data).
- 0b010000100: New-school "General" Transaction: contains Extra
(extension data), but no Signature.

For the New-school General Transaction, it becomes trivial for authors
to publish extensions to the mechanism for authorizing an Origin, e.g.
through new kinds of key-signing schemes, ZK proofs, pallet state,
mutations over pre-authenticated origins or any combination of the
above.

Wrap your `SignedExtension`s in `AsTransactionExtension`. This should be
accompanied by renaming your aggregate type in line with the new
terminology. E.g. Before:

```rust
/// The SignedExtension to the basic transaction logic.
pub type SignedExtra = (
	/* snip */
	MySpecialSignedExtension,
);
/// Unchecked extrinsic type as expected by this runtime.
pub type UncheckedExtrinsic =
	generic::UncheckedExtrinsic<Address, RuntimeCall, Signature, SignedExtra>;
```

After:

```rust
/// The extension to the basic transaction logic.
pub type TxExtension = (
	/* snip */
	AsTransactionExtension<MySpecialSignedExtension>,
);
/// Unchecked extrinsic type as expected by this runtime.
pub type UncheckedExtrinsic =
	generic::UncheckedExtrinsic<Address, RuntimeCall, Signature, TxExtension>;
```

You'll also need to alter any transaction building logic to add a
`.into()` to make the conversion happen. E.g. Before:

```rust
fn construct_extrinsic(
		/* snip */
) -> UncheckedExtrinsic {
	let extra: SignedExtra = (
		/* snip */
		MySpecialSignedExtension::new(/* snip */),
	);
	let payload = SignedPayload::new(call.clone(), extra.clone()).unwrap();
	let signature = payload.using_encoded(|e| sender.sign(e));
	UncheckedExtrinsic::new_signed(
		/* snip */
		Signature::Sr25519(signature),
		extra,
	)
}
```

After:

```rust
fn construct_extrinsic(
		/* snip */
) -> UncheckedExtrinsic {
	let tx_ext: TxExtension = (
		/* snip */
		MySpecialSignedExtension::new(/* snip */).into(),
	);
	let payload = SignedPayload::new(call.clone(), tx_ext.clone()).unwrap();
	let signature = payload.using_encoded(|e| sender.sign(e));
	UncheckedExtrinsic::new_signed(
		/* snip */
		Signature::Sr25519(signature),
		tx_ext,
	)
}
```

Most `SignedExtension`s can be trivially converted to become a
`TransactionExtension`. There are a few things to know.

- Instead of a single trait like `SignedExtension`, you should now
implement two traits individually: `TransactionExtensionBase` and
`TransactionExtension`.
- Weights are now a thing and must be provided via the new function `fn
weight`.

This trait takes care of anything which is not dependent on types
specific to your runtime, most notably `Call`.

- `AdditionalSigned`/`additional_signed` is renamed to
`Implicit`/`implicit`.
- Weight must be returned by implementing the `weight` function. If your
extension is associated with a pallet, you'll probably want to do this
via the pallet's existing benchmarking infrastructure.

Generally:
- `pre_dispatch` is now `prepare` and you *should not reexecute the
`validate` functionality in there*!
- You don't get an account ID any more; you get an origin instead. If
you need to presume an account ID, then you can use the trait function
`AsSystemOriginSigner::as_system_origin_signer`.
- You get an additional ticket, similar to `Pre`, called `Val`. This
defines data which is passed from `validate` into `prepare`. This is
important since you should not be duplicating logic from `validate` to
`prepare`, you need a way of passing your working from the former into
the latter. This is it.
- This trait takes two type parameters: `Call` and `Context`. `Call` is
the runtime call type which used to be an associated type; you can just
move it to become a type parameter for your trait impl. `Context` is not
currently used and you can safely implement over it as an unbounded
type.
- There's no `AccountId` associated type any more. Just remove it.

Regarding `validate`:
- You get three new parameters in `validate`; all can be ignored when
migrating from `SignedExtension`.
- `validate` returns a tuple on success; the second item in the tuple is
the new ticket type `Self::Val` which gets passed in to `prepare`. If
you use any information extracted during `validate` (off-chain and
on-chain, non-mutating) in `prepare` (on-chain, mutating) then you can
pass it through with this. For the tuple's last item, just return the
`origin` argument.

Regarding `prepare`:
- This is renamed from `pre_dispatch`, but there is one change:
- FUNCTIONALITY TO VALIDATE THE TRANSACTION NEED NOT BE DUPLICATED FROM
`validate`!!
- (This is different to `SignedExtension` which was required to run the
same checks in `pre_dispatch` as in `validate`.)

Regarding `post_dispatch`:
- Since there are no unsigned transactions handled by
`TransactionExtension`, `Pre` is always defined, so the first parameter
is `Self::Pre` rather than `Option<Self::Pre>`.

If you make use of `SignedExtension::validate_unsigned` or
`SignedExtension::pre_dispatch_unsigned`, then:
- Just use the regular versions of these functions instead.
- Have your logic execute in the case that the `origin` is `None`.
- Ensure your transaction creation logic creates a General Transaction
rather than a Bare Transaction; this means having to include all
`TransactionExtension`s' data.
- `ValidateUnsigned` can still be used (for now) if you need to be able
to construct transactions which contain none of the extension data,
however these will be phased out in stage 2 of the Transactions Horizon,
so you should consider moving to an extension-centric design.

- [x] Introduce `CheckSignature` impl of `TransactionExtension` to
ensure it's possible to have crypto be done wholly in a
`TransactionExtension`.
- [x] Deprecate `SignedExtension` and move all uses in codebase to
`TransactionExtension`.
  - [x] `ChargeTransactionPayment`
  - [x] `DummyExtension`
  - [x] `ChargeAssetTxPayment` (asset-tx-payment)
  - [x] `ChargeAssetTxPayment` (asset-conversion-tx-payment)
  - [x] `CheckWeight`
  - [x] `CheckTxVersion`
  - [x] `CheckSpecVersion`
  - [x] `CheckNonce`
  - [x] `CheckNonZeroSender`
  - [x] `CheckMortality`
  - [x] `CheckGenesis`
  - [x] `CheckOnlySudoAccount`
  - [x] `WatchDummy`
  - [x] `PrevalidateAttests`
  - [x] `GenericSignedExtension`
  - [x] `SignedExtension` (chain-polkadot-bulletin)
  - [x] `RefundSignedExtensionAdapter`
- [x] Implement `fn weight` across the board.
- [ ] Go through all pre-existing extensions which assume an account
signer and explicitly handle the possibility of another kind of origin.
- [x] `CheckNonce` should probably succeed in the case of a non-account
origin.
- [x] `CheckNonZeroSender` should succeed in the case of a non-account
origin.
- [x] `ChargeTransactionPayment` and family should fail in the case of a
non-account origin.
  - [ ]
- [x] Fix any broken tests.

---------

Signed-off-by: georgepisaltu <george.pisaltu@parity.io>
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
Signed-off-by: Alexandru Gheorghe <alexandru.gheorghe@parity.io>
Signed-off-by: Andrei Sandu <andrei-mihail@parity.io>
Co-authored-by: Nikhil Gupta <17176722+gupnik@users.noreply.github.com>
Co-authored-by: georgepisaltu <52418509+georgepisaltu@users.noreply.github.com>
Co-authored-by: Chevdor <chevdor@users.noreply.github.com>
Co-authored-by: Bastian Köcher <git@kchr.de>
Co-authored-by: Maciej <maciej.zyszkiewicz@parity.io>
Co-authored-by: Javier Viola <javier@parity.io>
Co-authored-by: Marcin S. <marcin@realemail.net>
Co-authored-by: Tsvetomir Dimitrov <tsvetomir@parity.io>
Co-authored-by: Javier Bullrich <javier@bullrich.dev>
Co-authored-by: Koute <koute@users.noreply.github.com>
Co-authored-by: Adrian Catangiu <adrian@parity.io>
Co-authored-by: Vladimir Istyufeev <vladimir@parity.io>
Co-authored-by: Ross Bulat <ross@parity.io>
Co-authored-by: Gonçalo Pestana <g6pestana@gmail.com>
Co-authored-by: Liam Aharon <liam.aharon@hotmail.com>
Co-authored-by: Svyatoslav Nikolsky <svyatonik@gmail.com>
Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com>
Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
Co-authored-by: s0me0ne-unkn0wn <48632512+s0me0ne-unkn0wn@users.noreply.github.com>
Co-authored-by: ordian <write@reusable.software>
Co-authored-by: Sebastian Kunert <skunert49@gmail.com>
Co-authored-by: Aaro Altonen <48052676+altonen@users.noreply.github.com>
Co-authored-by: Dmitry Markin <dmitry@markin.tech>
Co-authored-by: Alexandru Vasile <60601340+lexnv@users.noreply.github.com>
Co-authored-by: Alexander Samusev <41779041+alvicsam@users.noreply.github.com>
Co-authored-by: Julian Eager <eagr@tutanota.com>
Co-authored-by: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com>
Co-authored-by: Davide Galassi <davxy@datawok.net>
Co-authored-by: Dónal Murray <donal.murray@parity.io>
Co-authored-by: yjh <yjh465402634@gmail.com>
Co-authored-by: Tom Mi <tommi@niemi.lol>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Will | Paradox | ParaNodes.io <79228812+paradox-tt@users.noreply.github.com>
Co-authored-by: Bastian Köcher <info@kchr.de>
Co-authored-by: Joshy Orndorff <JoshOrndorff@users.noreply.github.com>
Co-authored-by: Joshy Orndorff <git-user-email.h0ly5@simplelogin.com>
Co-authored-by: PG Herveou <pgherveou@gmail.com>
Co-authored-by: Alexander Theißen <alex.theissen@me.com>
Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>
Co-authored-by: Juan Girini <juangirini@gmail.com>
Co-authored-by: bader y <ibnbassem@gmail.com>
Co-authored-by: James Wilson <james@jsdw.me>
Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>
Co-authored-by: asynchronous rob <rphmeier@gmail.com>
Co-authored-by: Parth <desaiparth08@gmail.com>
Co-authored-by: Andrew Jones <ascjones@gmail.com>
Co-authored-by: Jonathan Udd <jonathan@dwellir.com>
Co-authored-by: Serban Iorga <serban@parity.io>
Co-authored-by: Egor_P <egor@parity.io>
Co-authored-by: Branislav Kontur <bkontur@gmail.com>
Co-authored-by: Evgeny Snitko <evgeny@parity.io>
Co-authored-by: Just van Stam <vstam1@users.noreply.github.com>
Co-authored-by: Francisco Aguirre <franciscoaguirreperez@gmail.com>
Co-authored-by: gupnik <nikhilgupta.iitk@gmail.com>
Co-authored-by: dzmitry-lahoda <dzmitry@lahoda.pro>
Co-authored-by: zhiqiangxu <652732310@qq.com>
Co-authored-by: Nazar Mokrynskyi <nazar@mokrynskyi.com>
Co-authored-by: Anwesh <anweshknayak@gmail.com>
Co-authored-by: cheme <emericchevalier.pro@gmail.com>
Co-authored-by: Sam Johnson <sam@durosoft.com>
Co-authored-by: kianenigma <kian@parity.io>
Co-authored-by: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com>
Co-authored-by: Muharem <ismailov.m.h@gmail.com>
Co-authored-by: joepetrowski <joe@parity.io>
Co-authored-by: Alexandru Gheorghe <49718502+alexggh@users.noreply.github.com>
Co-authored-by: Gabriel Facco de Arruda <arrudagates@gmail.com>
Co-authored-by: Squirrel <gilescope@gmail.com>
Co-authored-by: Andrei Sandu <54316454+sandreim@users.noreply.github.com>
Co-authored-by: georgepisaltu <george.pisaltu@parity.io>
Co-authored-by: command-bot <>
(cherry picked from commit fd5f9292f500652e1d4792b09fb8ac60e1268ce4)

* Revert "FRAME: Create `TransactionExtension` as a replacement for `SignedExtension` (#2280)" (#3665)

This PR reverts #2280 which introduced `TransactionExtension` to replace
`SignedExtension`.

As a result of the discussion
[here](https://github.com/paritytech/polkadot-sdk/pull/3623#issuecomment-1986789700),
the changes will be reverted for now with plans to reintroduce the
concept in the future.

---------

Signed-off-by: georgepisaltu <george.pisaltu@parity.io>
(cherry picked from commit bbd51ce867967f71657b901f1a956ad4f75d352e)

* Increase timeout for assertions (#3680)

Prevents timeouts in ci like
https://gitlab.parity.io/parity/mirrors/polkadot-sdk/-/jobs/5516019

(cherry picked from commit c4c9257386036a9e27e7ee001fe8eadb80958cc0)

* Removes `as [disambiguation_path]` from `derive_impl` usage (#3652)

Step in https://github.com/paritytech/polkadot-sdk/issues/171

This PR removes `as [disambiguation_path]` syntax from `derive_impl`
usage across the polkadot-sdk as introduced in
https://github.com/paritytech/polkadot-sdk/pull/3505

(cherry picked from commit 7099f6e1b1fa3c8cd894693902263d9ed0e38978)

* Fix typo (#3691)

(cherry picked from commit 6b1179f13b4815685769c9f523720ec9ed0e2ff4)

* Bridge zombienet tests: remove unneeded accounts (#3700)

Bridge zombienet tests: remove unneeded accounts

(cherry picked from commit 0c6c837f689a287583508506e342ba07687e8d26)

* Fix typos (#3753)

(cherry picked from commit 7241a8db7b3496816503c6058dae67f66c666b00)

* Update polkadot-sdk refs

* Fix dependency conflicts

* Fix build

* cargo fmt

* Fix spellcheck test

---------

Co-authored-by: Svyatoslav Nikolsky <svyatonik@gmail.com>
Co-authored-by: Branislav Kontur <bkontur@gmail.com>
Co-authored-by: Marcin S <marcin@realemail.net>
Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
Co-authored-by: Gavin Wood <gavin@parity.io>
Co-authored-by: georgepisaltu <52418509+georgepisaltu@users.noreply.github.com>
Co-authored-by: Javier Viola <363911+pepoviola@users.noreply.github.com>
Co-authored-by: gupnik <nikhilgupta.iitk@gmail.com>
Co-authored-by: jokess123 <163112061+jokess123@users.noreply.github.com>
Co-authored-by: slicejoke <163888128+slicejoke@users.noreply.github.com>
---
 bridges/bin/runtime-common/Cargo.toml         |   1 -
 bridges/bin/runtime-common/src/lib.rs         |  72 +-
 bridges/bin/runtime-common/src/mock.rs        |   7 +-
 .../runtime-common/src/priority_calculator.rs |  13 +-
 .../src/refund_relayer_extension.rs           | 156 ++--
 bridges/modules/grandpa/README.md             |   2 +-
 bridges/modules/grandpa/src/mock.rs           |   2 +-
 bridges/modules/messages/src/mock.rs          |   4 +-
 bridges/modules/parachains/src/mock.rs        |   2 +-
 bridges/modules/relayers/src/mock.rs          |   4 +-
 .../modules/xcm-bridge-hub-router/src/mock.rs |   2 +-
 bridges/modules/xcm-bridge-hub/src/mock.rs    |   4 +-
 .../chain-bridge-hub-cumulus/src/lib.rs       |   4 +-
 .../chain-bridge-hub-rococo/src/lib.rs        |   2 +-
 bridges/primitives/chain-kusama/src/lib.rs    |   4 +-
 .../chain-polkadot-bulletin/src/lib.rs        |  51 +-
 bridges/primitives/chain-polkadot/src/lib.rs  |   4 +-
 bridges/primitives/chain-rococo/src/lib.rs    |   4 +-
 bridges/primitives/chain-westend/src/lib.rs   |   4 +-
 bridges/primitives/polkadot-core/Cargo.toml   |   2 +-
 bridges/primitives/polkadot-core/src/lib.rs   |  37 +-
 bridges/primitives/runtime/src/extensions.rs  | 135 ++--
 bridges/relays/bin-substrate/Cargo.toml       |   2 +-
 .../client-bridge-hub-kusama/src/lib.rs       |   9 +-
 .../client-bridge-hub-polkadot/src/lib.rs     |   9 +-
 .../client-bridge-hub-rococo/src/lib.rs       |   9 +-
 .../client-bridge-hub-westend/src/lib.rs      |   9 +-
 bridges/relays/client-kusama/src/lib.rs       |   6 +-
 .../client-polkadot-bulletin/src/lib.rs       |   8 +-
 bridges/relays/client-polkadot/src/lib.rs     |   6 +-
 bridges/relays/client-rococo/src/lib.rs       |   6 +-
 bridges/relays/client-substrate/Cargo.toml    |   2 +-
 bridges/relays/client-westend/src/lib.rs      |   6 +-
 bridges/relays/equivocation/Cargo.toml        |   2 +-
 bridges/relays/finality/Cargo.toml            |   2 +-
 bridges/relays/lib-substrate-relay/Cargo.toml |   2 +-
 bridges/relays/messages/Cargo.toml            |   2 +-
 bridges/relays/parachains/Cargo.toml          |   2 +-
 bridges/relays/utils/Cargo.toml               |   2 +-
 bridges/testing/README.md                     |  31 +
 .../bridge_hub_rococo_local_network.toml      |  88 ++
 .../bridge_hub_westend_local_network.toml     |  88 ++
 .../rococo-westend/bridges_rococo_westend.sh  | 401 +++++++++
 .../environments/rococo-westend/helper.sh     |   3 +
 .../rococo-westend/rococo-init.zndsl          |   8 +
 .../environments/rococo-westend/rococo.zndsl  |   7 +
 .../environments/rococo-westend/spawn.sh      |  70 ++
 .../rococo-westend/start_relayer.sh           |  23 +
 .../rococo-westend/westend-init.zndsl         |   7 +
 .../environments/rococo-westend/westend.zndsl |   6 +
 .../best-finalized-header-at-bridged-chain.js |  25 +
 .../js-helpers/chains/rococo-at-westend.js    |   6 +
 .../js-helpers/chains/westend-at-rococo.js    |   6 +
 .../native-assets-balance-increased.js        |  21 +
 ...only-mandatory-headers-synced-when-idle.js |  44 +
 .../only-required-headers-synced-when-idle.js |  81 ++
 .../framework/js-helpers/relayer-rewards.js   |  28 +
 bridges/testing/framework/js-helpers/utils.js | 103 +++
 .../js-helpers/wait-hrmp-channel-opened.js    |  22 +
 .../js-helpers/wrapped-assets-balance.js      |  26 +
 bridges/testing/framework/utils/bridges.sh    | 309 +++++++
 bridges/testing/framework/utils/common.sh     |  45 ++
 .../utils/generate_hex_encoded_call/index.js  | 165 ++++
 .../package-lock.json                         | 759 ++++++++++++++++++
 .../generate_hex_encoded_call/package.json    |  11 +
 bridges/testing/framework/utils/zombienet.sh  |  39 +
 bridges/testing/run-new-test.sh               |  48 ++
 bridges/testing/run-tests.sh                  | 138 ++++
 bridges/testing/scripts/invoke-script.sh      |   7 +
 bridges/testing/scripts/start-relayer.sh      |   7 +
 bridges/testing/scripts/sync-exit.sh          |  14 +
 .../roc-reaches-westend.zndsl                 |  12 +
 .../testing/tests/0001-asset-transfer/run.sh  |  25 +
 .../wnd-reaches-rococo.zndsl                  |  12 +
 .../wroc-reaches-rococo.zndsl                 |  10 +
 .../wwnd-reaches-westend.zndsl                |  10 +
 .../rococo-to-westend.zndsl                   |   8 +
 .../run.sh                                    |  35 +
 .../westend-to-rococo.zndsl                   |   7 +
 ...ynced-while-active-rococo-to-westend.zndsl |  26 +
 ...ynced-while-active-westend-to-rococo.zndsl |  26 +
 81 files changed, 3069 insertions(+), 338 deletions(-)
 create mode 100644 bridges/testing/README.md
 create mode 100644 bridges/testing/environments/rococo-westend/bridge_hub_rococo_local_network.toml
 create mode 100644 bridges/testing/environments/rococo-westend/bridge_hub_westend_local_network.toml
 create mode 100755 bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh
 create mode 100755 bridges/testing/environments/rococo-westend/helper.sh
 create mode 100644 bridges/testing/environments/rococo-westend/rococo-init.zndsl
 create mode 100644 bridges/testing/environments/rococo-westend/rococo.zndsl
 create mode 100755 bridges/testing/environments/rococo-westend/spawn.sh
 create mode 100755 bridges/testing/environments/rococo-westend/start_relayer.sh
 create mode 100644 bridges/testing/environments/rococo-westend/westend-init.zndsl
 create mode 100644 bridges/testing/environments/rococo-westend/westend.zndsl
 create mode 100644 bridges/testing/framework/js-helpers/best-finalized-header-at-bridged-chain.js
 create mode 100644 bridges/testing/framework/js-helpers/chains/rococo-at-westend.js
 create mode 100644 bridges/testing/framework/js-helpers/chains/westend-at-rococo.js
 create mode 100644 bridges/testing/framework/js-helpers/native-assets-balance-increased.js
 create mode 100644 bridges/testing/framework/js-helpers/only-mandatory-headers-synced-when-idle.js
 create mode 100644 bridges/testing/framework/js-helpers/only-required-headers-synced-when-idle.js
 create mode 100644 bridges/testing/framework/js-helpers/relayer-rewards.js
 create mode 100644 bridges/testing/framework/js-helpers/utils.js
 create mode 100644 bridges/testing/framework/js-helpers/wait-hrmp-channel-opened.js
 create mode 100644 bridges/testing/framework/js-helpers/wrapped-assets-balance.js
 create mode 100755 bridges/testing/framework/utils/bridges.sh
 create mode 100644 bridges/testing/framework/utils/common.sh
 create mode 100644 bridges/testing/framework/utils/generate_hex_encoded_call/index.js
 create mode 100644 bridges/testing/framework/utils/generate_hex_encoded_call/package-lock.json
 create mode 100644 bridges/testing/framework/utils/generate_hex_encoded_call/package.json
 create mode 100644 bridges/testing/framework/utils/zombienet.sh
 create mode 100755 bridges/testing/run-new-test.sh
 create mode 100755 bridges/testing/run-tests.sh
 create mode 100755 bridges/testing/scripts/invoke-script.sh
 create mode 100755 bridges/testing/scripts/start-relayer.sh
 create mode 100755 bridges/testing/scripts/sync-exit.sh
 create mode 100644 bridges/testing/tests/0001-asset-transfer/roc-reaches-westend.zndsl
 create mode 100755 bridges/testing/tests/0001-asset-transfer/run.sh
 create mode 100644 bridges/testing/tests/0001-asset-transfer/wnd-reaches-rococo.zndsl
 create mode 100644 bridges/testing/tests/0001-asset-transfer/wroc-reaches-rococo.zndsl
 create mode 100644 bridges/testing/tests/0001-asset-transfer/wwnd-reaches-westend.zndsl
 create mode 100644 bridges/testing/tests/0002-mandatory-headers-synced-while-idle/rococo-to-westend.zndsl
 create mode 100755 bridges/testing/tests/0002-mandatory-headers-synced-while-idle/run.sh
 create mode 100644 bridges/testing/tests/0002-mandatory-headers-synced-while-idle/westend-to-rococo.zndsl
 create mode 100644 bridges/testing/tests/0003-required-headers-synced-while-active-rococo-to-westend.zndsl
 create mode 100644 bridges/testing/tests/0003-required-headers-synced-while-active-westend-to-rococo.zndsl

diff --git a/bridges/bin/runtime-common/Cargo.toml b/bridges/bin/runtime-common/Cargo.toml
index 835a8cf1318..e3c05d1bebb 100644
--- a/bridges/bin/runtime-common/Cargo.toml
+++ b/bridges/bin/runtime-common/Cargo.toml
@@ -93,7 +93,6 @@ runtime-benchmarks = [
 	"pallet-bridge-messages/runtime-benchmarks",
 	"pallet-bridge-parachains/runtime-benchmarks",
 	"pallet-bridge-relayers/runtime-benchmarks",
-	"pallet-transaction-payment/runtime-benchmarks",
 	"pallet-utility/runtime-benchmarks",
 	"sp-runtime/runtime-benchmarks",
 	"xcm-builder/runtime-benchmarks",
diff --git a/bridges/bin/runtime-common/src/lib.rs b/bridges/bin/runtime-common/src/lib.rs
index 035077408c4..2722f6f1c6d 100644
--- a/bridges/bin/runtime-common/src/lib.rs
+++ b/bridges/bin/runtime-common/src/lib.rs
@@ -105,48 +105,43 @@ macro_rules! generate_bridge_reject_obsolete_headers_and_messages {
 	($call:ty, $account_id:ty, $($filter_call:ty),*) => {
 		#[derive(Clone, codec::Decode, Default, codec::Encode, Eq, PartialEq, sp_runtime::RuntimeDebug, scale_info::TypeInfo)]
 		pub struct BridgeRejectObsoleteHeadersAndMessages;
-		impl sp_runtime::traits::TransactionExtensionBase for BridgeRejectObsoleteHeadersAndMessages {
+		impl sp_runtime::traits::SignedExtension for BridgeRejectObsoleteHeadersAndMessages {
 			const IDENTIFIER: &'static str = "BridgeRejectObsoleteHeadersAndMessages";
-			type Implicit = ();
-		}
-		impl<Context> sp_runtime::traits::TransactionExtension<$call, Context> for BridgeRejectObsoleteHeadersAndMessages {
+			type AccountId = $account_id;
+			type Call = $call;
+			type AdditionalSigned = ();
 			type Pre = ();
-			type Val = ();
+
+			fn additional_signed(&self) -> sp_std::result::Result<
+				(),
+				sp_runtime::transaction_validity::TransactionValidityError,
+			> {
+				Ok(())
+			}
 
 			fn validate(
 				&self,
-				origin: <$call as sp_runtime::traits::Dispatchable>::RuntimeOrigin,
-				call: &$call,
-				_info: &sp_runtime::traits::DispatchInfoOf<$call>,
+				_who: &Self::AccountId,
+				call: &Self::Call,
+				_info: &sp_runtime::traits::DispatchInfoOf<Self::Call>,
 				_len: usize,
-				_context: &mut Context,
-				_self_implicit: Self::Implicit,
-				_inherited_implication: &impl codec::Encode,
-			) -> Result<
-				(
-					sp_runtime::transaction_validity::ValidTransaction,
-					Self::Val,
-					<$call as sp_runtime::traits::Dispatchable>::RuntimeOrigin,
-				), sp_runtime::transaction_validity::TransactionValidityError
-			> {
-				let tx_validity = sp_runtime::transaction_validity::ValidTransaction::default();
+			) -> sp_runtime::transaction_validity::TransactionValidity {
+				let valid = sp_runtime::transaction_validity::ValidTransaction::default();
 				$(
-					let call_filter_validity = <$filter_call as $crate::BridgeRuntimeFilterCall<$call>>::validate(call)?;
-					let tx_validity = tx_validity.combine_with(call_filter_validity);
+					let valid = valid
+						.combine_with(<$filter_call as $crate::BridgeRuntimeFilterCall<$call>>::validate(call)?);
 				)*
-				Ok((tx_validity, (), origin))
+				Ok(valid)
 			}
 
-			fn prepare(
+			fn pre_dispatch(
 				self,
-				_val: Self::Val,
-				_origin: &<$call as sp_runtime::traits::Dispatchable>::RuntimeOrigin,
-				_call: &$call,
-				_info: &sp_runtime::traits::DispatchInfoOf<$call>,
-				_len: usize,
-				_context: &Context,
+				who: &Self::AccountId,
+				call: &Self::Call,
+				info: &sp_runtime::traits::DispatchInfoOf<Self::Call>,
+				len: usize,
 			) -> Result<Self::Pre, sp_runtime::transaction_validity::TransactionValidityError> {
-				Ok(())
+				self.validate(who, call, info, len).map(drop)
 			}
 		}
 	};
@@ -155,14 +150,12 @@ macro_rules! generate_bridge_reject_obsolete_headers_and_messages {
 #[cfg(test)]
 mod tests {
 	use crate::BridgeRuntimeFilterCall;
-	use codec::Encode;
-	use frame_support::assert_err;
+	use frame_support::{assert_err, assert_ok};
 	use sp_runtime::{
-		traits::DispatchTransaction,
+		traits::SignedExtension,
 		transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction},
 	};
 
-	#[derive(Encode)]
 	pub struct MockCall {
 		data: u32,
 	}
@@ -213,20 +206,17 @@ mod tests {
 		);
 
 		assert_err!(
-			BridgeRejectObsoleteHeadersAndMessages.validate_only((), &MockCall { data: 1 }, &(), 0),
+			BridgeRejectObsoleteHeadersAndMessages.validate(&(), &MockCall { data: 1 }, &(), 0),
 			InvalidTransaction::Custom(1)
 		);
 
 		assert_err!(
-			BridgeRejectObsoleteHeadersAndMessages.validate_only((), &MockCall { data: 2 }, &(), 0),
+			BridgeRejectObsoleteHeadersAndMessages.validate(&(), &MockCall { data: 2 }, &(), 0),
 			InvalidTransaction::Custom(2)
 		);
 
-		assert_eq!(
-			BridgeRejectObsoleteHeadersAndMessages
-				.validate_only((), &MockCall { data: 3 }, &(), 0)
-				.unwrap()
-				.0,
+		assert_ok!(
+			BridgeRejectObsoleteHeadersAndMessages.validate(&(), &MockCall { data: 3 }, &(), 0),
 			ValidTransaction { priority: 3, ..Default::default() }
 		)
 	}
diff --git a/bridges/bin/runtime-common/src/mock.rs b/bridges/bin/runtime-common/src/mock.rs
index f147f1404f0..deee4524e85 100644
--- a/bridges/bin/runtime-common/src/mock.rs
+++ b/bridges/bin/runtime-common/src/mock.rs
@@ -141,7 +141,7 @@ parameter_types! {
 	pub const ReserveId: [u8; 8] = *b"brdgrlrs";
 }
 
-#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)]
+#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
 impl frame_system::Config for TestRuntime {
 	type Hash = ThisChainHash;
 	type Hashing = ThisChainHasher;
@@ -158,13 +158,13 @@ impl pallet_utility::Config for TestRuntime {
 	type WeightInfo = ();
 }
 
-#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig as pallet_balances::DefaultConfig)]
+#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
 impl pallet_balances::Config for TestRuntime {
 	type ReserveIdentifier = [u8; 8];
 	type AccountStore = System;
 }
 
-#[derive_impl(pallet_transaction_payment::config_preludes::TestDefaultConfig as pallet_transaction_payment::DefaultConfig)]
+#[derive_impl(pallet_transaction_payment::config_preludes::TestDefaultConfig)]
 impl pallet_transaction_payment::Config for TestRuntime {
 	type OnChargeTransaction = pallet_transaction_payment::CurrencyAdapter<Balances, ()>;
 	type OperationalFeeMultiplier = ConstU8<5>;
@@ -177,6 +177,7 @@ impl pallet_transaction_payment::Config for TestRuntime {
 		MinimumMultiplier,
 		MaximumMultiplier,
 	>;
+	type RuntimeEvent = RuntimeEvent;
 }
 
 impl pallet_bridge_grandpa::Config for TestRuntime {
diff --git a/bridges/bin/runtime-common/src/priority_calculator.rs b/bridges/bin/runtime-common/src/priority_calculator.rs
index 0c53018330e..c2737128e34 100644
--- a/bridges/bin/runtime-common/src/priority_calculator.rs
+++ b/bridges/bin/runtime-common/src/priority_calculator.rs
@@ -128,7 +128,7 @@ mod integrity_tests {
 		Runtime::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
 		BalanceOf<Runtime>: Send + Sync + FixedPointOperand,
 	{
-		// esimate priority of transaction that delivers one message and has large tip
+		// estimate priority of transaction that delivers one message and has large tip
 		let maximal_messages_in_delivery_transaction =
 			Runtime::MaxUnconfirmedMessagesAtInboundLane::get();
 		let small_with_tip_priority =
@@ -169,15 +169,12 @@ mod integrity_tests {
 		// nodes to the proof (x0.5 because we expect some nodes to be reused)
 		let estimated_message_size = 512;
 		// let's say all our messages have the same dispatch weight
-		let estimated_message_dispatch_weight = <Runtime as pallet_bridge_messages::Config<
-			MessagesInstance,
-		>>::WeightInfo::message_dispatch_weight(
-			estimated_message_size
-		);
+		let estimated_message_dispatch_weight =
+			Runtime::WeightInfo::message_dispatch_weight(estimated_message_size);
 		// messages proof argument size is (for every message) messages size + some additional
 		// trie nodes. Some of them are reused by different messages, so let's take 2/3 of default
 		// "overhead" constant
-		let messages_proof_size = <Runtime as pallet_bridge_messages::Config<MessagesInstance>>::WeightInfo::expected_extra_storage_proof_size()
+		let messages_proof_size = Runtime::WeightInfo::expected_extra_storage_proof_size()
 			.saturating_mul(2)
 			.saturating_div(3)
 			.saturating_add(estimated_message_size)
@@ -185,7 +182,7 @@ mod integrity_tests {
 
 		// finally we are able to estimate transaction size and weight
 		let transaction_size = base_tx_size.saturating_add(messages_proof_size);
-		let transaction_weight = <Runtime as pallet_bridge_messages::Config<MessagesInstance>>::WeightInfo::receive_messages_proof_weight(
+		let transaction_weight = Runtime::WeightInfo::receive_messages_proof_weight(
 			&PreComputedSize(transaction_size as _),
 			messages as _,
 			estimated_message_dispatch_weight.saturating_mul(messages),
diff --git a/bridges/bin/runtime-common/src/refund_relayer_extension.rs b/bridges/bin/runtime-common/src/refund_relayer_extension.rs
index b912f8445ac..8e901d72821 100644
--- a/bridges/bin/runtime-common/src/refund_relayer_extension.rs
+++ b/bridges/bin/runtime-common/src/refund_relayer_extension.rs
@@ -16,7 +16,7 @@
 
 //! Signed extension that refunds relayer if he has delivered some new messages.
 //! It also refunds transaction cost if the transaction is an `utility.batchAll()`
-//! with calls that are: delivering new messsage and all necessary underlying headers
+//! with calls that are: delivering new message and all necessary underlying headers
 //! (parachain or relay chain).
 
 use crate::messages_call_ext::{
@@ -48,12 +48,9 @@ use pallet_transaction_payment::{Config as TransactionPaymentConfig, OnChargeTra
 use pallet_utility::{Call as UtilityCall, Config as UtilityConfig, Pallet as UtilityPallet};
 use scale_info::TypeInfo;
 use sp_runtime::{
-	traits::{
-		AsSystemOriginSigner, DispatchInfoOf, Dispatchable, Get, PostDispatchInfoOf,
-		TransactionExtension, TransactionExtensionBase, ValidateResult, Zero,
-	},
+	traits::{DispatchInfoOf, Dispatchable, Get, PostDispatchInfoOf, SignedExtension, Zero},
 	transaction_validity::{
-		InvalidTransaction, TransactionPriority, TransactionValidityError, ValidTransactionBuilder,
+		TransactionPriority, TransactionValidity, TransactionValidityError, ValidTransactionBuilder,
 	},
 	DispatchResult, FixedPointOperand, RuntimeDebug,
 };
@@ -242,8 +239,8 @@ pub enum RelayerAccountAction<AccountId, Reward> {
 	Slash(AccountId, RewardsAccountParams),
 }
 
-/// Everything common among our refund transaction extensions.
-pub trait RefundTransactionExtension:
+/// Everything common among our refund signed extensions.
+pub trait RefundSignedExtension:
 	'static + Clone + Codec + sp_std::fmt::Debug + Default + Eq + PartialEq + Send + Sync + TypeInfo
 where
 	<Self::Runtime as GrandpaConfig<Self::GrandpaInstance>>::BridgedChain:
@@ -459,8 +456,8 @@ where
 	}
 }
 
-/// Adapter that allow implementing `sp_runtime::traits::TransactionExtension` for any
-/// `RefundTransactionExtension`.
+/// Adapter that allow implementing `sp_runtime::traits::SignedExtension` for any
+/// `RefundSignedExtension`.
 #[derive(
 	DefaultNoBound,
 	CloneNoBound,
@@ -471,13 +468,12 @@ where
 	RuntimeDebugNoBound,
 	TypeInfo,
 )]
-pub struct RefundTransactionExtensionAdapter<T: RefundTransactionExtension>(T)
+pub struct RefundSignedExtensionAdapter<T: RefundSignedExtension>(T)
 where
 	<T::Runtime as GrandpaConfig<T::GrandpaInstance>>::BridgedChain:
 		Chain<BlockNumber = RelayBlockNumber>;
 
-impl<T: RefundTransactionExtension> TransactionExtensionBase
-	for RefundTransactionExtensionAdapter<T>
+impl<T: RefundSignedExtension> SignedExtension for RefundSignedExtensionAdapter<T>
 where
 	<T::Runtime as GrandpaConfig<T::GrandpaInstance>>::BridgedChain:
 		Chain<BlockNumber = RelayBlockNumber>,
@@ -487,35 +483,22 @@ where
 		+ MessagesCallSubType<T::Runtime, <T::Msgs as RefundableMessagesLaneId>::Instance>,
 {
 	const IDENTIFIER: &'static str = T::Id::STR;
-	type Implicit = ();
-}
-
-impl<T: RefundTransactionExtension, Context> TransactionExtension<CallOf<T::Runtime>, Context>
-	for RefundTransactionExtensionAdapter<T>
-where
-	<T::Runtime as GrandpaConfig<T::GrandpaInstance>>::BridgedChain:
-		Chain<BlockNumber = RelayBlockNumber>,
-	CallOf<T::Runtime>: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>
-		+ IsSubType<CallableCallFor<UtilityPallet<T::Runtime>, T::Runtime>>
-		+ GrandpaCallSubType<T::Runtime, T::GrandpaInstance>
-		+ MessagesCallSubType<T::Runtime, <T::Msgs as RefundableMessagesLaneId>::Instance>,
-	<CallOf<T::Runtime> as Dispatchable>::RuntimeOrigin:
-		AsSystemOriginSigner<AccountIdOf<T::Runtime>> + Clone,
-{
+	type AccountId = AccountIdOf<T::Runtime>;
+	type Call = CallOf<T::Runtime>;
+	type AdditionalSigned = ();
 	type Pre = Option<PreDispatchData<AccountIdOf<T::Runtime>>>;
-	type Val = Option<CallInfo>;
+
+	fn additional_signed(&self) -> Result<(), TransactionValidityError> {
+		Ok(())
+	}
 
 	fn validate(
 		&self,
-		origin: <CallOf<T::Runtime> as Dispatchable>::RuntimeOrigin,
-		call: &CallOf<T::Runtime>,
-		_info: &DispatchInfoOf<CallOf<T::Runtime>>,
+		who: &Self::AccountId,
+		call: &Self::Call,
+		_info: &DispatchInfoOf<Self::Call>,
 		_len: usize,
-		_context: &mut Context,
-		_self_implicit: Self::Implicit,
-		_inherited_implication: &impl Encode,
-	) -> ValidateResult<Self::Val, CallOf<T::Runtime>> {
-		let who = origin.as_system_origin_signer().ok_or(InvalidTransaction::BadSigner)?;
+	) -> TransactionValidity {
 		// this is the only relevant line of code for the `pre_dispatch`
 		//
 		// we're not calling `validate` from `pre_dispatch` directly because of performance
@@ -528,12 +511,12 @@ where
 		// we only boost priority of presumably correct message delivery transactions
 		let bundled_messages = match T::bundled_messages_for_priority_boost(parsed_call.as_ref()) {
 			Some(bundled_messages) => bundled_messages,
-			None => return Ok((Default::default(), parsed_call, origin)),
+			None => return Ok(Default::default()),
 		};
 
 		// we only boost priority if relayer has staked required balance
 		if !RelayersPallet::<T::Runtime>::is_registration_active(who) {
-			return Ok((Default::default(), parsed_call, origin))
+			return Ok(Default::default())
 		}
 
 		// compute priority boost
@@ -552,21 +535,20 @@ where
 			priority_boost,
 		);
 
-		let validity = valid_transaction.build()?;
-		Ok((validity, parsed_call, origin))
+		valid_transaction.build()
 	}
 
-	fn prepare(
+	fn pre_dispatch(
 		self,
-		val: Self::Val,
-		origin: &<CallOf<T::Runtime> as Dispatchable>::RuntimeOrigin,
-		_call: &CallOf<T::Runtime>,
-		_info: &DispatchInfoOf<CallOf<T::Runtime>>,
+		who: &Self::AccountId,
+		call: &Self::Call,
+		_info: &DispatchInfoOf<Self::Call>,
 		_len: usize,
-		_context: &Context,
 	) -> Result<Self::Pre, TransactionValidityError> {
-		let who = origin.as_system_origin_signer().ok_or(InvalidTransaction::BadSigner)?;
-		Ok(val.map(|call_info| {
+		// this is a relevant piece of `validate` that we need here (in `pre_dispatch`)
+		let parsed_call = T::parse_and_check_for_obsolete_call(call)?;
+
+		Ok(parsed_call.map(|call_info| {
 			log::trace!(
 				target: "runtime::bridge",
 				"{} via {:?} parsed bridge transaction in pre-dispatch: {:?}",
@@ -579,14 +561,13 @@ where
 	}
 
 	fn post_dispatch(
-		pre: Self::Pre,
-		info: &DispatchInfoOf<CallOf<T::Runtime>>,
-		post_info: &PostDispatchInfoOf<CallOf<T::Runtime>>,
+		pre: Option<Self::Pre>,
+		info: &DispatchInfoOf<Self::Call>,
+		post_info: &PostDispatchInfoOf<Self::Call>,
 		len: usize,
 		result: &DispatchResult,
-		_context: &Context,
 	) -> Result<(), TransactionValidityError> {
-		let call_result = T::analyze_call_result(Some(pre), info, post_info, len, result);
+		let call_result = T::analyze_call_result(pre, info, post_info, len, result);
 
 		match call_result {
 			RelayerAccountAction::None => (),
@@ -614,7 +595,7 @@ where
 	}
 }
 
-/// Transaction extension that refunds a relayer for new messages coming from a parachain.
+/// Signed extension that refunds a relayer for new messages coming from a parachain.
 ///
 /// Also refunds relayer for successful finality delivery if it comes in batch (`utility.batchAll`)
 /// with message delivery transaction. Batch may deliver either both relay chain header and
@@ -655,7 +636,7 @@ pub struct RefundBridgedParachainMessages<Runtime, Para, Msgs, Refund, Priority,
 	)>,
 );
 
-impl<Runtime, Para, Msgs, Refund, Priority, Id> RefundTransactionExtension
+impl<Runtime, Para, Msgs, Refund, Priority, Id> RefundSignedExtension
 	for RefundBridgedParachainMessages<Runtime, Para, Msgs, Refund, Priority, Id>
 where
 	Self: 'static + Send + Sync,
@@ -749,13 +730,13 @@ where
 	}
 }
 
-/// Transaction extension that refunds a relayer for new messages coming from a standalone (GRANDPA)
+/// Signed extension that refunds a relayer for new messages coming from a standalone (GRANDPA)
 /// chain.
 ///
 /// Also refunds relayer for successful finality delivery if it comes in batch (`utility.batchAll`)
 /// with message delivery transaction. Batch may deliver either both relay chain header and
-/// parachain head, or just parachain head. Corresponding headers must be used in messages proof
-/// verification.
+/// parachain head, or just parachain head. Corresponding headers must be used in messages
+/// proof verification.
 ///
 /// Extension does not refund transaction tip due to security reasons.
 #[derive(
@@ -790,7 +771,7 @@ pub struct RefundBridgedGrandpaMessages<Runtime, GrandpaInstance, Msgs, Refund,
 	)>,
 );
 
-impl<Runtime, GrandpaInstance, Msgs, Refund, Priority, Id> RefundTransactionExtension
+impl<Runtime, GrandpaInstance, Msgs, Refund, Priority, Id> RefundSignedExtension
 	for RefundBridgedGrandpaMessages<Runtime, GrandpaInstance, Msgs, Refund, Priority, Id>
 where
 	Self: 'static + Send + Sync,
@@ -888,8 +869,8 @@ mod tests {
 		Call as ParachainsCall, Pallet as ParachainsPallet, RelayBlockHash,
 	};
 	use sp_runtime::{
-		traits::{ConstU64, DispatchTransaction, Header as HeaderT},
-		transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction},
+		traits::{ConstU64, Header as HeaderT},
+		transaction_validity::{InvalidTransaction, ValidTransaction},
 		DispatchError,
 	};
 
@@ -918,7 +899,7 @@ mod tests {
 		ConstU64<1>,
 		StrTestExtension,
 	>;
-	type TestGrandpaExtension = RefundTransactionExtensionAdapter<TestGrandpaExtensionProvider>;
+	type TestGrandpaExtension = RefundSignedExtensionAdapter<TestGrandpaExtensionProvider>;
 	type TestExtensionProvider = RefundBridgedParachainMessages<
 		TestRuntime,
 		DefaultRefundableParachainId<(), TestParachain>,
@@ -927,7 +908,7 @@ mod tests {
 		ConstU64<1>,
 		StrTestExtension,
 	>;
-	type TestExtension = RefundTransactionExtensionAdapter<TestExtensionProvider>;
+	type TestExtension = RefundSignedExtensionAdapter<TestExtensionProvider>;
 
 	fn initial_balance_of_relayer_account_at_this_chain() -> ThisChainBalance {
 		let test_stake: ThisChainBalance = TestStake::get();
@@ -1426,28 +1407,14 @@ mod tests {
 
 	fn run_validate(call: RuntimeCall) -> TransactionValidity {
 		let extension: TestExtension =
-			RefundTransactionExtensionAdapter(RefundBridgedParachainMessages(PhantomData));
-		extension
-			.validate_only(
-				Some(relayer_account_at_this_chain()).into(),
-				&call,
-				&DispatchInfo::default(),
-				0,
-			)
-			.map(|res| res.0)
+			RefundSignedExtensionAdapter(RefundBridgedParachainMessages(PhantomData));
+		extension.validate(&relayer_account_at_this_chain(), &call, &DispatchInfo::default(), 0)
 	}
 
 	fn run_grandpa_validate(call: RuntimeCall) -> TransactionValidity {
 		let extension: TestGrandpaExtension =
-			RefundTransactionExtensionAdapter(RefundBridgedGrandpaMessages(PhantomData));
-		extension
-			.validate_only(
-				Some(relayer_account_at_this_chain()).into(),
-				&call,
-				&DispatchInfo::default(),
-				0,
-			)
-			.map(|res| res.0)
+			RefundSignedExtensionAdapter(RefundBridgedGrandpaMessages(PhantomData));
+		extension.validate(&relayer_account_at_this_chain(), &call, &DispatchInfo::default(), 0)
 	}
 
 	fn run_validate_ignore_priority(call: RuntimeCall) -> TransactionValidity {
@@ -1461,30 +1428,16 @@ mod tests {
 		call: RuntimeCall,
 	) -> Result<Option<PreDispatchData<ThisChainAccountId>>, TransactionValidityError> {
 		let extension: TestExtension =
-			RefundTransactionExtensionAdapter(RefundBridgedParachainMessages(PhantomData));
-		extension
-			.validate_and_prepare(
-				Some(relayer_account_at_this_chain()).into(),
-				&call,
-				&DispatchInfo::default(),
-				0,
-			)
-			.map(|(pre, _)| pre)
+			RefundSignedExtensionAdapter(RefundBridgedParachainMessages(PhantomData));
+		extension.pre_dispatch(&relayer_account_at_this_chain(), &call, &DispatchInfo::default(), 0)
 	}
 
 	fn run_grandpa_pre_dispatch(
 		call: RuntimeCall,
 	) -> Result<Option<PreDispatchData<ThisChainAccountId>>, TransactionValidityError> {
 		let extension: TestGrandpaExtension =
-			RefundTransactionExtensionAdapter(RefundBridgedGrandpaMessages(PhantomData));
-		extension
-			.validate_and_prepare(
-				Some(relayer_account_at_this_chain()).into(),
-				&call,
-				&DispatchInfo::default(),
-				0,
-			)
-			.map(|(pre, _)| pre)
+			RefundSignedExtensionAdapter(RefundBridgedGrandpaMessages(PhantomData));
+		extension.pre_dispatch(&relayer_account_at_this_chain(), &call, &DispatchInfo::default(), 0)
 	}
 
 	fn dispatch_info() -> DispatchInfo {
@@ -1507,12 +1460,11 @@ mod tests {
 		dispatch_result: DispatchResult,
 	) {
 		let post_dispatch_result = TestExtension::post_dispatch(
-			pre_dispatch_data,
+			Some(pre_dispatch_data),
 			&dispatch_info(),
 			&post_dispatch_info(),
 			1024,
 			&dispatch_result,
-			&(),
 		);
 		assert_eq!(post_dispatch_result, Ok(()));
 	}
diff --git a/bridges/modules/grandpa/README.md b/bridges/modules/grandpa/README.md
index 43ee5c316d1..992bd2cc472 100644
--- a/bridges/modules/grandpa/README.md
+++ b/bridges/modules/grandpa/README.md
@@ -27,7 +27,7 @@ for provided header.
 There are two main things in GRANDPA that help building light clients:
 
 - there's no need to import all headers of the bridged chain. Light client may import finalized headers or just
-  some of finalized headders that it consider useful. While the validators set stays the same, the client may
+  some of finalized headers that it consider useful. While the validators set stays the same, the client may
   import any header that is finalized by this set;
 
 - when validators set changes, the GRANDPA gadget adds next set to the header. So light client doesn't need to
diff --git a/bridges/modules/grandpa/src/mock.rs b/bridges/modules/grandpa/src/mock.rs
index e41e89341b3..4318d663a2e 100644
--- a/bridges/modules/grandpa/src/mock.rs
+++ b/bridges/modules/grandpa/src/mock.rs
@@ -42,7 +42,7 @@ construct_runtime! {
 	}
 }
 
-#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)]
+#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
 impl frame_system::Config for TestRuntime {
 	type Block = Block;
 }
diff --git a/bridges/modules/messages/src/mock.rs b/bridges/modules/messages/src/mock.rs
index af921205398..ec63f15b94b 100644
--- a/bridges/modules/messages/src/mock.rs
+++ b/bridges/modules/messages/src/mock.rs
@@ -77,14 +77,14 @@ frame_support::construct_runtime! {
 
 pub type DbWeight = RocksDbWeight;
 
-#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)]
+#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
 impl frame_system::Config for TestRuntime {
 	type Block = Block;
 	type AccountData = pallet_balances::AccountData<Balance>;
 	type DbWeight = DbWeight;
 }
 
-#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig as pallet_balances::DefaultConfig)]
+#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
 impl pallet_balances::Config for TestRuntime {
 	type ReserveIdentifier = [u8; 8];
 	type AccountStore = System;
diff --git a/bridges/modules/parachains/src/mock.rs b/bridges/modules/parachains/src/mock.rs
index 143f11d9863..3af3fd3e763 100644
--- a/bridges/modules/parachains/src/mock.rs
+++ b/bridges/modules/parachains/src/mock.rs
@@ -161,7 +161,7 @@ construct_runtime! {
 	}
 }
 
-#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)]
+#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
 impl frame_system::Config for TestRuntime {
 	type Block = Block;
 }
diff --git a/bridges/modules/relayers/src/mock.rs b/bridges/modules/relayers/src/mock.rs
index 667b10e5c12..3124787896c 100644
--- a/bridges/modules/relayers/src/mock.rs
+++ b/bridges/modules/relayers/src/mock.rs
@@ -59,14 +59,14 @@ parameter_types! {
 	pub const Lease: BlockNumber = 8;
 }
 
-#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)]
+#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
 impl frame_system::Config for TestRuntime {
 	type Block = Block;
 	type AccountData = pallet_balances::AccountData<Balance>;
 	type DbWeight = DbWeight;
 }
 
-#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig as pallet_balances::DefaultConfig)]
+#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
 impl pallet_balances::Config for TestRuntime {
 	type ReserveIdentifier = [u8; 8];
 	type AccountStore = System;
diff --git a/bridges/modules/xcm-bridge-hub-router/src/mock.rs b/bridges/modules/xcm-bridge-hub-router/src/mock.rs
index 6dbfba5f6fd..54e10966d51 100644
--- a/bridges/modules/xcm-bridge-hub-router/src/mock.rs
+++ b/bridges/modules/xcm-bridge-hub-router/src/mock.rs
@@ -64,7 +64,7 @@ parameter_types! {
 	pub UnknownXcmVersionLocation: Location = Location::new(2, [GlobalConsensus(BridgedNetworkId::get()), Parachain(9999)]);
 }
 
-#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)]
+#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
 impl frame_system::Config for TestRuntime {
 	type Block = Block;
 }
diff --git a/bridges/modules/xcm-bridge-hub/src/mock.rs b/bridges/modules/xcm-bridge-hub/src/mock.rs
index e40e1f9fb65..4c09bce56d7 100644
--- a/bridges/modules/xcm-bridge-hub/src/mock.rs
+++ b/bridges/modules/xcm-bridge-hub/src/mock.rs
@@ -64,7 +64,7 @@ parameter_types! {
 	pub const ExistentialDeposit: Balance = 1;
 }
 
-#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)]
+#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
 impl frame_system::Config for TestRuntime {
 	type AccountId = AccountId;
 	type AccountData = pallet_balances::AccountData<Balance>;
@@ -72,7 +72,7 @@ impl frame_system::Config for TestRuntime {
 	type Lookup = IdentityLookup<Self::AccountId>;
 }
 
-#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig as pallet_balances::DefaultConfig)]
+#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
 impl pallet_balances::Config for TestRuntime {
 	type AccountStore = System;
 }
diff --git a/bridges/primitives/chain-bridge-hub-cumulus/src/lib.rs b/bridges/primitives/chain-bridge-hub-cumulus/src/lib.rs
index f186f6427ae..c49aa4b8563 100644
--- a/bridges/primitives/chain-bridge-hub-cumulus/src/lib.rs
+++ b/bridges/primitives/chain-bridge-hub-cumulus/src/lib.rs
@@ -26,7 +26,7 @@ pub use bp_polkadot_core::{
 };
 
 use bp_messages::*;
-use bp_polkadot_core::SuffixedCommonTransactionExtension;
+use bp_polkadot_core::SuffixedCommonSignedExtension;
 use bp_runtime::extensions::{
 	BridgeRejectObsoleteHeadersAndMessages, RefundBridgedParachainMessagesSchema,
 };
@@ -164,7 +164,7 @@ pub const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = 1024;
 pub const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = 4096;
 
 /// Signed extension that is used by all bridge hubs.
-pub type TransactionExtension = SuffixedCommonTransactionExtension<(
+pub type SignedExtension = SuffixedCommonSignedExtension<(
 	BridgeRejectObsoleteHeadersAndMessages,
 	RefundBridgedParachainMessagesSchema,
 )>;
diff --git a/bridges/primitives/chain-bridge-hub-rococo/src/lib.rs b/bridges/primitives/chain-bridge-hub-rococo/src/lib.rs
index 992ef1bd7a1..c4e697fbe95 100644
--- a/bridges/primitives/chain-bridge-hub-rococo/src/lib.rs
+++ b/bridges/primitives/chain-bridge-hub-rococo/src/lib.rs
@@ -107,5 +107,5 @@ frame_support::parameter_types! {
 
 	/// Transaction fee that is paid at the Rococo BridgeHub for delivering single outbound message confirmation.
 	/// (initially was calculated by test `BridgeHubRococo::can_calculate_fee_for_complex_message_confirmation_transaction` + `33%`)
-	pub const BridgeHubRococoBaseConfirmationFeeInRocs: u128 = 5_380_904_835;
+	pub const BridgeHubRococoBaseConfirmationFeeInRocs: u128 = 5_380_829_647;
 }
diff --git a/bridges/primitives/chain-kusama/src/lib.rs b/bridges/primitives/chain-kusama/src/lib.rs
index 253a1010e83..e3b4d0520f6 100644
--- a/bridges/primitives/chain-kusama/src/lib.rs
+++ b/bridges/primitives/chain-kusama/src/lib.rs
@@ -59,8 +59,8 @@ impl ChainWithGrandpa for Kusama {
 	const AVERAGE_HEADER_SIZE: u32 = AVERAGE_HEADER_SIZE;
 }
 
-// The TransactionExtension used by Kusama.
-pub use bp_polkadot_core::CommonTransactionExtension as TransactionExtension;
+// The SignedExtension used by Kusama.
+pub use bp_polkadot_core::CommonSignedExtension as SignedExtension;
 
 /// Name of the parachains pallet in the Kusama runtime.
 pub const PARAS_PALLET_NAME: &str = "Paras";
diff --git a/bridges/primitives/chain-polkadot-bulletin/src/lib.rs b/bridges/primitives/chain-polkadot-bulletin/src/lib.rs
index 73dd122bd15..f2eebf93124 100644
--- a/bridges/primitives/chain-polkadot-bulletin/src/lib.rs
+++ b/bridges/primitives/chain-polkadot-bulletin/src/lib.rs
@@ -25,7 +25,7 @@ use bp_runtime::{
 	decl_bridge_finality_runtime_apis, decl_bridge_messages_runtime_apis,
 	extensions::{
 		CheckEra, CheckGenesis, CheckNonZeroSender, CheckNonce, CheckSpecVersion, CheckTxVersion,
-		CheckWeight, GenericTransactionExtension, GenericTransactionExtensionSchema,
+		CheckWeight, GenericSignedExtension, GenericSignedExtensionSchema,
 	},
 	Chain, ChainId, TransactionEra,
 };
@@ -37,12 +37,7 @@ use frame_support::{
 };
 use frame_system::limits;
 use scale_info::TypeInfo;
-use sp_runtime::{
-	impl_tx_ext_default,
-	traits::{Dispatchable, TransactionExtensionBase},
-	transaction_validity::TransactionValidityError,
-	Perbill,
-};
+use sp_runtime::{traits::DispatchInfoOf, transaction_validity::TransactionValidityError, Perbill};
 
 // This chain reuses most of Polkadot primitives.
 pub use bp_polkadot_core::{
@@ -76,10 +71,10 @@ pub const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = 1024;
 pub const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = 4096;
 
 /// This signed extension is used to ensure that the chain transactions are signed by proper
-pub type ValidateSigned = GenericTransactionExtensionSchema<(), ()>;
+pub type ValidateSigned = GenericSignedExtensionSchema<(), ()>;
 
 /// Signed extension schema, used by Polkadot Bulletin.
-pub type TransactionExtensionSchema = GenericTransactionExtension<(
+pub type SignedExtensionSchema = GenericSignedExtension<(
 	(
 		CheckNonZeroSender,
 		CheckSpecVersion,
@@ -92,30 +87,34 @@ pub type TransactionExtensionSchema = GenericTransactionExtension<(
 	ValidateSigned,
 )>;
 
-/// Transaction extension, used by Polkadot Bulletin.
+/// Signed extension, used by Polkadot Bulletin.
 #[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
-pub struct TransactionExtension(TransactionExtensionSchema);
+pub struct SignedExtension(SignedExtensionSchema);
 
-impl TransactionExtensionBase for TransactionExtension {
+impl sp_runtime::traits::SignedExtension for SignedExtension {
 	const IDENTIFIER: &'static str = "Not needed.";
-	type Implicit = <TransactionExtensionSchema as TransactionExtensionBase>::Implicit;
+	type AccountId = ();
+	type Call = ();
+	type AdditionalSigned =
+		<SignedExtensionSchema as sp_runtime::traits::SignedExtension>::AdditionalSigned;
+	type Pre = ();
 
-	fn implicit(&self) -> Result<Self::Implicit, TransactionValidityError> {
-		<TransactionExtensionSchema as TransactionExtensionBase>::implicit(&self.0)
+	fn additional_signed(&self) -> Result<Self::AdditionalSigned, TransactionValidityError> {
+		self.0.additional_signed()
 	}
-}
 
-impl<C, Context> sp_runtime::traits::TransactionExtension<C, Context> for TransactionExtension
-where
-	C: Dispatchable,
-{
-	type Pre = ();
-	type Val = ();
-
-	impl_tx_ext_default!(C; Context; validate prepare);
+	fn pre_dispatch(
+		self,
+		_who: &Self::AccountId,
+		_call: &Self::Call,
+		_info: &DispatchInfoOf<Self::Call>,
+		_len: usize,
+	) -> Result<Self::Pre, TransactionValidityError> {
+		Ok(())
+	}
 }
 
-impl TransactionExtension {
+impl SignedExtension {
 	/// Create signed extension from its components.
 	pub fn from_params(
 		spec_version: u32,
@@ -124,7 +123,7 @@ impl TransactionExtension {
 		genesis_hash: Hash,
 		nonce: Nonce,
 	) -> Self {
-		Self(GenericTransactionExtension::new(
+		Self(GenericSignedExtension::new(
 			(
 				(
 					(),              // non-zero sender
diff --git a/bridges/primitives/chain-polkadot/src/lib.rs b/bridges/primitives/chain-polkadot/src/lib.rs
index e5e2e7c3a04..fc5e10308a8 100644
--- a/bridges/primitives/chain-polkadot/src/lib.rs
+++ b/bridges/primitives/chain-polkadot/src/lib.rs
@@ -61,8 +61,8 @@ impl ChainWithGrandpa for Polkadot {
 	const AVERAGE_HEADER_SIZE: u32 = AVERAGE_HEADER_SIZE;
 }
 
-/// The TransactionExtension used by Polkadot.
-pub type TransactionExtension = SuffixedCommonTransactionExtension<PrevalidateAttests>;
+/// The SignedExtension used by Polkadot.
+pub type SignedExtension = SuffixedCommonSignedExtension<PrevalidateAttests>;
 
 /// Name of the parachains pallet in the Polkadot runtime.
 pub const PARAS_PALLET_NAME: &str = "Paras";
diff --git a/bridges/primitives/chain-rococo/src/lib.rs b/bridges/primitives/chain-rococo/src/lib.rs
index 267c6b2b1f0..f1b256f0f09 100644
--- a/bridges/primitives/chain-rococo/src/lib.rs
+++ b/bridges/primitives/chain-rococo/src/lib.rs
@@ -59,8 +59,8 @@ impl ChainWithGrandpa for Rococo {
 	const AVERAGE_HEADER_SIZE: u32 = AVERAGE_HEADER_SIZE;
 }
 
-// The TransactionExtension used by Rococo.
-pub use bp_polkadot_core::CommonTransactionExtension as TransactionExtension;
+// The SignedExtension used by Rococo.
+pub use bp_polkadot_core::CommonSignedExtension as SignedExtension;
 
 /// Name of the parachains pallet in the Rococo runtime.
 pub const PARAS_PALLET_NAME: &str = "Paras";
diff --git a/bridges/primitives/chain-westend/src/lib.rs b/bridges/primitives/chain-westend/src/lib.rs
index afa02e8ee54..f03fd2160a7 100644
--- a/bridges/primitives/chain-westend/src/lib.rs
+++ b/bridges/primitives/chain-westend/src/lib.rs
@@ -59,8 +59,8 @@ impl ChainWithGrandpa for Westend {
 	const AVERAGE_HEADER_SIZE: u32 = AVERAGE_HEADER_SIZE;
 }
 
-// The TransactionExtension used by Westend.
-pub use bp_polkadot_core::CommonTransactionExtension as TransactionExtension;
+// The SignedExtension used by Westend.
+pub use bp_polkadot_core::CommonSignedExtension as SignedExtension;
 
 /// Name of the parachains pallet in the Rococo runtime.
 pub const PARAS_PALLET_NAME: &str = "Paras";
diff --git a/bridges/primitives/polkadot-core/Cargo.toml b/bridges/primitives/polkadot-core/Cargo.toml
index dd3912429d6..4851ce14c0d 100644
--- a/bridges/primitives/polkadot-core/Cargo.toml
+++ b/bridges/primitives/polkadot-core/Cargo.toml
@@ -13,7 +13,7 @@ workspace = true
 codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false, features = ["derive"] }
 parity-util-mem = { version = "0.12.0", optional = true }
 scale-info = { version = "2.11.0", default-features = false, features = ["derive"] }
-serde = { default-features = false, features = ["derive"], optional = true, workspace = true }
+serde = { optional = true, features = ["derive"], workspace = true, default-features = true }
 
 # Bridge Dependencies
 
diff --git a/bridges/primitives/polkadot-core/src/lib.rs b/bridges/primitives/polkadot-core/src/lib.rs
index d59b99db4b5..df2836495bb 100644
--- a/bridges/primitives/polkadot-core/src/lib.rs
+++ b/bridges/primitives/polkadot-core/src/lib.rs
@@ -24,8 +24,8 @@ use bp_runtime::{
 	self,
 	extensions::{
 		ChargeTransactionPayment, CheckEra, CheckGenesis, CheckNonZeroSender, CheckNonce,
-		CheckSpecVersion, CheckTxVersion, CheckWeight, GenericTransactionExtension,
-		TransactionExtensionSchema,
+		CheckSpecVersion, CheckTxVersion, CheckWeight, GenericSignedExtension,
+		SignedExtensionSchema,
 	},
 	EncodedOrDecodedCall, StorageMapKeyProvider, TransactionEra,
 };
@@ -229,12 +229,8 @@ pub type SignedBlock = generic::SignedBlock<Block>;
 pub type Balance = u128;
 
 /// Unchecked Extrinsic type.
-pub type UncheckedExtrinsic<Call, TransactionExt> = generic::UncheckedExtrinsic<
-	AccountAddress,
-	EncodedOrDecodedCall<Call>,
-	Signature,
-	TransactionExt,
->;
+pub type UncheckedExtrinsic<Call, SignedExt> =
+	generic::UncheckedExtrinsic<AccountAddress, EncodedOrDecodedCall<Call>, Signature, SignedExt>;
 
 /// Account address, used by the Polkadot-like chain.
 pub type Address = MultiAddress<AccountId, ()>;
@@ -279,7 +275,7 @@ impl AccountInfoStorageMapKeyProvider {
 }
 
 /// Extra signed extension data that is used by most chains.
-pub type CommonTransactionExtra = (
+pub type CommonSignedExtra = (
 	CheckNonZeroSender,
 	CheckSpecVersion,
 	CheckTxVersion,
@@ -290,12 +286,12 @@ pub type CommonTransactionExtra = (
 	ChargeTransactionPayment<Balance>,
 );
 
-/// Extra transaction extension data that starts with `CommonTransactionExtra`.
-pub type SuffixedCommonTransactionExtension<Suffix> =
-	GenericTransactionExtension<(CommonTransactionExtra, Suffix)>;
+/// Extra signed extension data that starts with `CommonSignedExtra`.
+pub type SuffixedCommonSignedExtension<Suffix> =
+	GenericSignedExtension<(CommonSignedExtra, Suffix)>;
 
-/// Helper trait to define some extra methods on `SuffixedCommonTransactionExtension`.
-pub trait SuffixedCommonTransactionExtensionExt<Suffix: TransactionExtensionSchema> {
+/// Helper trait to define some extra methods on `SuffixedCommonSignedExtension`.
+pub trait SuffixedCommonSignedExtensionExt<Suffix: SignedExtensionSchema> {
 	/// Create signed extension from its components.
 	fn from_params(
 		spec_version: u32,
@@ -304,7 +300,7 @@ pub trait SuffixedCommonTransactionExtensionExt<Suffix: TransactionExtensionSche
 		genesis_hash: Hash,
 		nonce: Nonce,
 		tip: Balance,
-		extra: (Suffix::Payload, Suffix::Implicit),
+		extra: (Suffix::Payload, Suffix::AdditionalSigned),
 	) -> Self;
 
 	/// Return transaction nonce.
@@ -314,10 +310,9 @@ pub trait SuffixedCommonTransactionExtensionExt<Suffix: TransactionExtensionSche
 	fn tip(&self) -> Balance;
 }
 
-impl<Suffix> SuffixedCommonTransactionExtensionExt<Suffix>
-	for SuffixedCommonTransactionExtension<Suffix>
+impl<Suffix> SuffixedCommonSignedExtensionExt<Suffix> for SuffixedCommonSignedExtension<Suffix>
 where
-	Suffix: TransactionExtensionSchema,
+	Suffix: SignedExtensionSchema,
 {
 	fn from_params(
 		spec_version: u32,
@@ -326,9 +321,9 @@ where
 		genesis_hash: Hash,
 		nonce: Nonce,
 		tip: Balance,
-		extra: (Suffix::Payload, Suffix::Implicit),
+		extra: (Suffix::Payload, Suffix::AdditionalSigned),
 	) -> Self {
-		GenericTransactionExtension::new(
+		GenericSignedExtension::new(
 			(
 				(
 					(),              // non-zero sender
@@ -370,7 +365,7 @@ where
 }
 
 /// Signed extension that is used by most chains.
-pub type CommonTransactionExtension = SuffixedCommonTransactionExtension<()>;
+pub type CommonSignedExtension = SuffixedCommonSignedExtension<()>;
 
 #[cfg(test)]
 mod tests {
diff --git a/bridges/primitives/runtime/src/extensions.rs b/bridges/primitives/runtime/src/extensions.rs
index a31e7b5bb47..d896bc92eff 100644
--- a/bridges/primitives/runtime/src/extensions.rs
+++ b/bridges/primitives/runtime/src/extensions.rs
@@ -20,138 +20,135 @@ use codec::{Compact, Decode, Encode};
 use impl_trait_for_tuples::impl_for_tuples;
 use scale_info::{StaticTypeInfo, TypeInfo};
 use sp_runtime::{
-	impl_tx_ext_default,
-	traits::{Dispatchable, TransactionExtension, TransactionExtensionBase},
+	traits::{DispatchInfoOf, SignedExtension},
 	transaction_validity::TransactionValidityError,
 };
 use sp_std::{fmt::Debug, marker::PhantomData};
 
-/// Trait that describes some properties of a `TransactionExtension` that are needed in order to
-/// send a transaction to the chain.
-pub trait TransactionExtensionSchema:
-	Encode + Decode + Debug + Eq + Clone + StaticTypeInfo
-{
+/// Trait that describes some properties of a `SignedExtension` that are needed in order to send a
+/// transaction to the chain.
+pub trait SignedExtensionSchema: Encode + Decode + Debug + Eq + Clone + StaticTypeInfo {
 	/// A type of the data encoded as part of the transaction.
 	type Payload: Encode + Decode + Debug + Eq + Clone + StaticTypeInfo;
 	/// Parameters which are part of the payload used to produce transaction signature,
 	/// but don't end up in the transaction itself (i.e. inherent part of the runtime).
-	type Implicit: Encode + Decode + Debug + Eq + Clone + StaticTypeInfo;
+	type AdditionalSigned: Encode + Debug + Eq + Clone + StaticTypeInfo;
 }
 
-impl TransactionExtensionSchema for () {
+impl SignedExtensionSchema for () {
 	type Payload = ();
-	type Implicit = ();
+	type AdditionalSigned = ();
 }
 
-/// An implementation of `TransactionExtensionSchema` using generic params.
+/// An implementation of `SignedExtensionSchema` using generic params.
 #[derive(Encode, Decode, Clone, Debug, PartialEq, Eq, TypeInfo)]
-pub struct GenericTransactionExtensionSchema<P, S>(PhantomData<(P, S)>);
+pub struct GenericSignedExtensionSchema<P, S>(PhantomData<(P, S)>);
 
-impl<P, S> TransactionExtensionSchema for GenericTransactionExtensionSchema<P, S>
+impl<P, S> SignedExtensionSchema for GenericSignedExtensionSchema<P, S>
 where
 	P: Encode + Decode + Debug + Eq + Clone + StaticTypeInfo,
-	S: Encode + Decode + Debug + Eq + Clone + StaticTypeInfo,
+	S: Encode + Debug + Eq + Clone + StaticTypeInfo,
 {
 	type Payload = P;
-	type Implicit = S;
+	type AdditionalSigned = S;
 }
 
-/// The `TransactionExtensionSchema` for `frame_system::CheckNonZeroSender`.
-pub type CheckNonZeroSender = GenericTransactionExtensionSchema<(), ()>;
+/// The `SignedExtensionSchema` for `frame_system::CheckNonZeroSender`.
+pub type CheckNonZeroSender = GenericSignedExtensionSchema<(), ()>;
 
-/// The `TransactionExtensionSchema` for `frame_system::CheckSpecVersion`.
-pub type CheckSpecVersion = GenericTransactionExtensionSchema<(), u32>;
+/// The `SignedExtensionSchema` for `frame_system::CheckSpecVersion`.
+pub type CheckSpecVersion = GenericSignedExtensionSchema<(), u32>;
 
-/// The `TransactionExtensionSchema` for `frame_system::CheckTxVersion`.
-pub type CheckTxVersion = GenericTransactionExtensionSchema<(), u32>;
+/// The `SignedExtensionSchema` for `frame_system::CheckTxVersion`.
+pub type CheckTxVersion = GenericSignedExtensionSchema<(), u32>;
 
-/// The `TransactionExtensionSchema` for `frame_system::CheckGenesis`.
-pub type CheckGenesis<Hash> = GenericTransactionExtensionSchema<(), Hash>;
+/// The `SignedExtensionSchema` for `frame_system::CheckGenesis`.
+pub type CheckGenesis<Hash> = GenericSignedExtensionSchema<(), Hash>;
 
-/// The `TransactionExtensionSchema` for `frame_system::CheckEra`.
-pub type CheckEra<Hash> = GenericTransactionExtensionSchema<sp_runtime::generic::Era, Hash>;
+/// The `SignedExtensionSchema` for `frame_system::CheckEra`.
+pub type CheckEra<Hash> = GenericSignedExtensionSchema<sp_runtime::generic::Era, Hash>;
 
-/// The `TransactionExtensionSchema` for `frame_system::CheckNonce`.
-pub type CheckNonce<TxNonce> = GenericTransactionExtensionSchema<Compact<TxNonce>, ()>;
+/// The `SignedExtensionSchema` for `frame_system::CheckNonce`.
+pub type CheckNonce<TxNonce> = GenericSignedExtensionSchema<Compact<TxNonce>, ()>;
 
-/// The `TransactionExtensionSchema` for `frame_system::CheckWeight`.
-pub type CheckWeight = GenericTransactionExtensionSchema<(), ()>;
+/// The `SignedExtensionSchema` for `frame_system::CheckWeight`.
+pub type CheckWeight = GenericSignedExtensionSchema<(), ()>;
 
-/// The `TransactionExtensionSchema` for `pallet_transaction_payment::ChargeTransactionPayment`.
-pub type ChargeTransactionPayment<Balance> =
-	GenericTransactionExtensionSchema<Compact<Balance>, ()>;
+/// The `SignedExtensionSchema` for `pallet_transaction_payment::ChargeTransactionPayment`.
+pub type ChargeTransactionPayment<Balance> = GenericSignedExtensionSchema<Compact<Balance>, ()>;
 
-/// The `TransactionExtensionSchema` for `polkadot-runtime-common::PrevalidateAttests`.
-pub type PrevalidateAttests = GenericTransactionExtensionSchema<(), ()>;
+/// The `SignedExtensionSchema` for `polkadot-runtime-common::PrevalidateAttests`.
+pub type PrevalidateAttests = GenericSignedExtensionSchema<(), ()>;
 
-/// The `TransactionExtensionSchema` for `BridgeRejectObsoleteHeadersAndMessages`.
-pub type BridgeRejectObsoleteHeadersAndMessages = GenericTransactionExtensionSchema<(), ()>;
+/// The `SignedExtensionSchema` for `BridgeRejectObsoleteHeadersAndMessages`.
+pub type BridgeRejectObsoleteHeadersAndMessages = GenericSignedExtensionSchema<(), ()>;
 
-/// The `TransactionExtensionSchema` for `RefundBridgedParachainMessages`.
+/// The `SignedExtensionSchema` for `RefundBridgedParachainMessages`.
 /// This schema is dedicated for `RefundBridgedParachainMessages` signed extension as
 /// wildcard/placeholder, which relies on the scale encoding for `()` or `((), ())`, or `((), (),
 /// ())` is the same. So runtime can contains any kind of tuple:
 /// `(BridgeRefundBridgeHubRococoMessages)`
 /// `(BridgeRefundBridgeHubRococoMessages, BridgeRefundBridgeHubWestendMessages)`
 /// `(BridgeRefundParachainMessages1, ..., BridgeRefundParachainMessagesN)`
-pub type RefundBridgedParachainMessagesSchema = GenericTransactionExtensionSchema<(), ()>;
+pub type RefundBridgedParachainMessagesSchema = GenericSignedExtensionSchema<(), ()>;
 
 #[impl_for_tuples(1, 12)]
-impl TransactionExtensionSchema for Tuple {
+impl SignedExtensionSchema for Tuple {
 	for_tuples!( type Payload = ( #( Tuple::Payload ),* ); );
-	for_tuples!( type Implicit = ( #( Tuple::Implicit ),* ); );
+	for_tuples!( type AdditionalSigned = ( #( Tuple::AdditionalSigned ),* ); );
 }
 
 /// A simplified version of signed extensions meant for producing signed transactions
 /// and signed payloads in the client code.
 #[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
-pub struct GenericTransactionExtension<S: TransactionExtensionSchema> {
+pub struct GenericSignedExtension<S: SignedExtensionSchema> {
 	/// A payload that is included in the transaction.
 	pub payload: S::Payload,
 	#[codec(skip)]
 	// It may be set to `None` if extensions are decoded. We are never reconstructing transactions
-	// (and it makes no sense to do that) => decoded version of `TransactionExtensions` is only
-	// used to read fields of the `payload`. And when resigning transaction, we're reconstructing
-	// `TransactionExtensions` from scratch.
-	implicit: Option<S::Implicit>,
+	// (and it makes no sense to do that) => decoded version of `SignedExtensions` is only used to
+	// read fields of the `payload`. And when resigning transaction, we're reconstructing
+	// `SignedExtensions` from scratch.
+	additional_signed: Option<S::AdditionalSigned>,
 }
 
-impl<S: TransactionExtensionSchema> GenericTransactionExtension<S> {
-	/// Create new `GenericTransactionExtension` object.
-	pub fn new(payload: S::Payload, implicit: Option<S::Implicit>) -> Self {
-		Self { payload, implicit }
+impl<S: SignedExtensionSchema> GenericSignedExtension<S> {
+	/// Create new `GenericSignedExtension` object.
+	pub fn new(payload: S::Payload, additional_signed: Option<S::AdditionalSigned>) -> Self {
+		Self { payload, additional_signed }
 	}
 }
 
-impl<S> TransactionExtensionBase for GenericTransactionExtension<S>
+impl<S> SignedExtension for GenericSignedExtension<S>
 where
-	S: TransactionExtensionSchema,
+	S: SignedExtensionSchema,
 	S::Payload: Send + Sync,
-	S::Implicit: Send + Sync,
+	S::AdditionalSigned: Send + Sync,
 {
 	const IDENTIFIER: &'static str = "Not needed.";
-	type Implicit = S::Implicit;
+	type AccountId = ();
+	type Call = ();
+	type AdditionalSigned = S::AdditionalSigned;
+	type Pre = ();
 
-	fn implicit(&self) -> Result<Self::Implicit, TransactionValidityError> {
+	fn additional_signed(&self) -> Result<Self::AdditionalSigned, TransactionValidityError> {
 		// we shall not ever see this error in relay, because we are never signing decoded
 		// transactions. Instead we're constructing and signing new transactions. So the error code
 		// is kinda random here
-		self.implicit
-			.clone()
-			.ok_or(frame_support::unsigned::TransactionValidityError::Unknown(
+		self.additional_signed.clone().ok_or(
+			frame_support::unsigned::TransactionValidityError::Unknown(
 				frame_support::unsigned::UnknownTransaction::Custom(0xFF),
-			))
+			),
+		)
 	}
-}
-impl<S, C, Context> TransactionExtension<C, Context> for GenericTransactionExtension<S>
-where
-	C: Dispatchable,
-	S: TransactionExtensionSchema,
-	S::Payload: Send + Sync,
-	S::Implicit: Send + Sync,
-{
-	type Pre = ();
-	type Val = ();
 
-	impl_tx_ext_default!(C; Context; validate prepare);
+	fn pre_dispatch(
+		self,
+		_who: &Self::AccountId,
+		_call: &Self::Call,
+		_info: &DispatchInfoOf<Self::Call>,
+		_len: usize,
+	) -> Result<Self::Pre, TransactionValidityError> {
+		Ok(())
+	}
 }
diff --git a/bridges/relays/bin-substrate/Cargo.toml b/bridges/relays/bin-substrate/Cargo.toml
index d5873752e22..2b89ce4f198 100644
--- a/bridges/relays/bin-substrate/Cargo.toml
+++ b/bridges/relays/bin-substrate/Cargo.toml
@@ -11,7 +11,7 @@ workspace = true
 [dependencies]
 anyhow = "1.0"
 async-std = "1.9.0"
-async-trait = "0.1"
+async-trait = "0.1.74"
 codec = { package = "parity-scale-codec", version = "3.1.5" }
 env_logger = "0.11"
 futures = "0.3.30"
diff --git a/bridges/relays/client-bridge-hub-kusama/src/lib.rs b/bridges/relays/client-bridge-hub-kusama/src/lib.rs
index 80f621dee30..43dd53d2d83 100644
--- a/bridges/relays/client-bridge-hub-kusama/src/lib.rs
+++ b/bridges/relays/client-bridge-hub-kusama/src/lib.rs
@@ -18,8 +18,8 @@
 
 pub mod codegen_runtime;
 
-use bp_bridge_hub_kusama::{TransactionExtension, AVERAGE_BLOCK_INTERVAL};
-use bp_polkadot::SuffixedCommonTransactionExtensionExt;
+use bp_bridge_hub_kusama::{SignedExtension, AVERAGE_BLOCK_INTERVAL};
+use bp_polkadot::SuffixedCommonSignedExtensionExt;
 use codec::Encode;
 use relay_substrate_client::{
 	calls::UtilityCall as MockUtilityCall, Chain, ChainWithBalances, ChainWithMessages,
@@ -37,8 +37,7 @@ pub type RuntimeCall = runtime_types::bridge_hub_kusama_runtime::RuntimeCall;
 pub type BridgeMessagesCall = runtime_types::pallet_bridge_messages::pallet::Call;
 pub type BridgeGrandpaCall = runtime_types::pallet_bridge_grandpa::pallet::Call;
 pub type BridgeParachainCall = runtime_types::pallet_bridge_parachains::pallet::Call;
-type UncheckedExtrinsic =
-	bp_bridge_hub_kusama::UncheckedExtrinsic<RuntimeCall, TransactionExtension>;
+type UncheckedExtrinsic = bp_bridge_hub_kusama::UncheckedExtrinsic<RuntimeCall, SignedExtension>;
 type UtilityCall = runtime_types::pallet_utility::pallet::Call;
 
 /// Kusama chain definition
@@ -88,7 +87,7 @@ impl ChainWithTransactions for BridgeHubKusama {
 	) -> Result<Self::SignedTransaction, SubstrateError> {
 		let raw_payload = SignedPayload::new(
 			unsigned.call,
-			TransactionExtension::from_params(
+			SignedExtension::from_params(
 				param.spec_version,
 				param.transaction_version,
 				unsigned.era,
diff --git a/bridges/relays/client-bridge-hub-polkadot/src/lib.rs b/bridges/relays/client-bridge-hub-polkadot/src/lib.rs
index ed147e92d5b..88b69065f84 100644
--- a/bridges/relays/client-bridge-hub-polkadot/src/lib.rs
+++ b/bridges/relays/client-bridge-hub-polkadot/src/lib.rs
@@ -18,8 +18,8 @@
 
 pub mod codegen_runtime;
 
-use bp_bridge_hub_polkadot::{TransactionExtension, AVERAGE_BLOCK_INTERVAL};
-use bp_polkadot_core::SuffixedCommonTransactionExtensionExt;
+use bp_bridge_hub_polkadot::{SignedExtension, AVERAGE_BLOCK_INTERVAL};
+use bp_polkadot_core::SuffixedCommonSignedExtensionExt;
 use codec::Encode;
 use relay_substrate_client::{
 	calls::UtilityCall as MockUtilityCall, Chain, ChainWithBalances, ChainWithMessages,
@@ -41,8 +41,7 @@ pub type BridgeKusamaMessagesCall = runtime_types::pallet_bridge_messages::palle
 pub type BridgePolkadotBulletinGrandpaCall = runtime_types::pallet_bridge_grandpa::pallet::Call;
 pub type BridgeKusamaGrandpaCall = runtime_types::pallet_bridge_grandpa::pallet::Call;
 pub type BridgeParachainCall = runtime_types::pallet_bridge_parachains::pallet::Call;
-type UncheckedExtrinsic =
-	bp_bridge_hub_polkadot::UncheckedExtrinsic<RuntimeCall, TransactionExtension>;
+type UncheckedExtrinsic = bp_bridge_hub_polkadot::UncheckedExtrinsic<RuntimeCall, SignedExtension>;
 type UtilityCall = runtime_types::pallet_utility::pallet::Call;
 
 /// Polkadot chain definition
@@ -92,7 +91,7 @@ impl ChainWithTransactions for BridgeHubPolkadot {
 	) -> Result<Self::SignedTransaction, SubstrateError> {
 		let raw_payload = SignedPayload::new(
 			unsigned.call,
-			TransactionExtension::from_params(
+			SignedExtension::from_params(
 				param.spec_version,
 				param.transaction_version,
 				unsigned.era,
diff --git a/bridges/relays/client-bridge-hub-rococo/src/lib.rs b/bridges/relays/client-bridge-hub-rococo/src/lib.rs
index 169135646d9..cae9e4f77a5 100644
--- a/bridges/relays/client-bridge-hub-rococo/src/lib.rs
+++ b/bridges/relays/client-bridge-hub-rococo/src/lib.rs
@@ -18,8 +18,8 @@
 
 pub mod codegen_runtime;
 
-use bp_bridge_hub_rococo::{TransactionExtension, AVERAGE_BLOCK_INTERVAL};
-use bp_polkadot_core::SuffixedCommonTransactionExtensionExt;
+use bp_bridge_hub_rococo::{SignedExtension, AVERAGE_BLOCK_INTERVAL};
+use bp_polkadot_core::SuffixedCommonSignedExtensionExt;
 use codec::Encode;
 use relay_substrate_client::{
 	calls::UtilityCall as MockUtilityCall, Chain, ChainWithBalances, ChainWithMessages,
@@ -39,8 +39,7 @@ pub type BridgeBulletinMessagesCall = runtime_types::pallet_bridge_messages::pal
 pub type BridgeGrandpaCall = runtime_types::pallet_bridge_grandpa::pallet::Call;
 pub type BridgeBulletinGrandpaCall = runtime_types::pallet_bridge_grandpa::pallet::Call2;
 pub type BridgeParachainCall = runtime_types::pallet_bridge_parachains::pallet::Call;
-type UncheckedExtrinsic =
-	bp_bridge_hub_rococo::UncheckedExtrinsic<RuntimeCall, TransactionExtension>;
+type UncheckedExtrinsic = bp_bridge_hub_rococo::UncheckedExtrinsic<RuntimeCall, SignedExtension>;
 type UtilityCall = runtime_types::pallet_utility::pallet::Call;
 
 /// Rococo chain definition
@@ -90,7 +89,7 @@ impl ChainWithTransactions for BridgeHubRococo {
 	) -> Result<Self::SignedTransaction, SubstrateError> {
 		let raw_payload = SignedPayload::new(
 			unsigned.call,
-			TransactionExtension::from_params(
+			SignedExtension::from_params(
 				param.spec_version,
 				param.transaction_version,
 				unsigned.era,
diff --git a/bridges/relays/client-bridge-hub-westend/src/lib.rs b/bridges/relays/client-bridge-hub-westend/src/lib.rs
index 5e4462f863a..049fa464960 100644
--- a/bridges/relays/client-bridge-hub-westend/src/lib.rs
+++ b/bridges/relays/client-bridge-hub-westend/src/lib.rs
@@ -18,8 +18,8 @@
 
 pub mod codegen_runtime;
 
-use bp_bridge_hub_westend::{TransactionExtension, AVERAGE_BLOCK_INTERVAL};
-use bp_polkadot_core::SuffixedCommonTransactionExtensionExt;
+use bp_bridge_hub_westend::{SignedExtension, AVERAGE_BLOCK_INTERVAL};
+use bp_polkadot_core::SuffixedCommonSignedExtensionExt;
 use codec::Encode;
 use relay_substrate_client::{
 	calls::UtilityCall as MockUtilityCall, Chain, ChainWithBalances, ChainWithMessages,
@@ -37,8 +37,7 @@ pub type RuntimeCall = runtime_types::bridge_hub_westend_runtime::RuntimeCall;
 pub type BridgeMessagesCall = runtime_types::pallet_bridge_messages::pallet::Call;
 pub type BridgeGrandpaCall = runtime_types::pallet_bridge_grandpa::pallet::Call;
 pub type BridgeParachainCall = runtime_types::pallet_bridge_parachains::pallet::Call;
-type UncheckedExtrinsic =
-	bp_bridge_hub_westend::UncheckedExtrinsic<RuntimeCall, TransactionExtension>;
+type UncheckedExtrinsic = bp_bridge_hub_westend::UncheckedExtrinsic<RuntimeCall, SignedExtension>;
 type UtilityCall = runtime_types::pallet_utility::pallet::Call;
 
 /// Westend chain definition
@@ -88,7 +87,7 @@ impl ChainWithTransactions for BridgeHubWestend {
 	) -> Result<Self::SignedTransaction, SubstrateError> {
 		let raw_payload = SignedPayload::new(
 			unsigned.call,
-			TransactionExtension::from_params(
+			SignedExtension::from_params(
 				param.spec_version,
 				param.transaction_version,
 				unsigned.era,
diff --git a/bridges/relays/client-kusama/src/lib.rs b/bridges/relays/client-kusama/src/lib.rs
index a953a383e12..24a6adfe95d 100644
--- a/bridges/relays/client-kusama/src/lib.rs
+++ b/bridges/relays/client-kusama/src/lib.rs
@@ -19,7 +19,7 @@
 pub mod codegen_runtime;
 
 use bp_kusama::{AccountInfoStorageMapKeyProvider, KUSAMA_SYNCED_HEADERS_GRANDPA_INFO_METHOD};
-use bp_polkadot_core::SuffixedCommonTransactionExtensionExt;
+use bp_polkadot_core::SuffixedCommonSignedExtensionExt;
 use codec::Encode;
 use relay_substrate_client::{
 	Chain, ChainWithBalances, ChainWithGrandpa, ChainWithRuntimeVersion, ChainWithTransactions,
@@ -84,7 +84,7 @@ impl RelayChain for Kusama {
 impl ChainWithTransactions for Kusama {
 	type AccountKeyPair = sp_core::sr25519::Pair;
 	type SignedTransaction =
-		bp_polkadot_core::UncheckedExtrinsic<Self::Call, bp_kusama::TransactionExtension>;
+		bp_polkadot_core::UncheckedExtrinsic<Self::Call, bp_kusama::SignedExtension>;
 
 	fn sign_transaction(
 		param: SignParam<Self>,
@@ -92,7 +92,7 @@ impl ChainWithTransactions for Kusama {
 	) -> Result<Self::SignedTransaction, SubstrateError> {
 		let raw_payload = SignedPayload::new(
 			unsigned.call,
-			bp_kusama::TransactionExtension::from_params(
+			bp_kusama::SignedExtension::from_params(
 				param.spec_version,
 				param.transaction_version,
 				unsigned.era,
diff --git a/bridges/relays/client-polkadot-bulletin/src/lib.rs b/bridges/relays/client-polkadot-bulletin/src/lib.rs
index a6cdd8ee675..1f18b25a905 100644
--- a/bridges/relays/client-polkadot-bulletin/src/lib.rs
+++ b/bridges/relays/client-polkadot-bulletin/src/lib.rs
@@ -100,10 +100,8 @@ impl ChainWithBalances for PolkadotBulletin {
 
 impl ChainWithTransactions for PolkadotBulletin {
 	type AccountKeyPair = sp_core::sr25519::Pair;
-	type SignedTransaction = bp_polkadot_bulletin::UncheckedExtrinsic<
-		Self::Call,
-		bp_polkadot_bulletin::TransactionExtension,
-	>;
+	type SignedTransaction =
+		bp_polkadot_bulletin::UncheckedExtrinsic<Self::Call, bp_polkadot_bulletin::SignedExtension>;
 
 	fn sign_transaction(
 		param: SignParam<Self>,
@@ -111,7 +109,7 @@ impl ChainWithTransactions for PolkadotBulletin {
 	) -> Result<Self::SignedTransaction, SubstrateError> {
 		let raw_payload = SignedPayload::new(
 			unsigned.call,
-			bp_polkadot_bulletin::TransactionExtension::from_params(
+			bp_polkadot_bulletin::SignedExtension::from_params(
 				param.spec_version,
 				param.transaction_version,
 				unsigned.era,
diff --git a/bridges/relays/client-polkadot/src/lib.rs b/bridges/relays/client-polkadot/src/lib.rs
index af75f0dbb87..9b655528b6e 100644
--- a/bridges/relays/client-polkadot/src/lib.rs
+++ b/bridges/relays/client-polkadot/src/lib.rs
@@ -19,7 +19,7 @@
 mod codegen_runtime;
 
 use bp_polkadot::{AccountInfoStorageMapKeyProvider, POLKADOT_SYNCED_HEADERS_GRANDPA_INFO_METHOD};
-use bp_polkadot_core::SuffixedCommonTransactionExtensionExt;
+use bp_polkadot_core::SuffixedCommonSignedExtensionExt;
 use codec::Encode;
 use relay_substrate_client::{
 	Chain, ChainWithBalances, ChainWithGrandpa, ChainWithRuntimeVersion, ChainWithTransactions,
@@ -84,7 +84,7 @@ impl RelayChain for Polkadot {
 impl ChainWithTransactions for Polkadot {
 	type AccountKeyPair = sp_core::sr25519::Pair;
 	type SignedTransaction =
-		bp_polkadot_core::UncheckedExtrinsic<Self::Call, bp_polkadot::TransactionExtension>;
+		bp_polkadot_core::UncheckedExtrinsic<Self::Call, bp_polkadot::SignedExtension>;
 
 	fn sign_transaction(
 		param: SignParam<Self>,
@@ -92,7 +92,7 @@ impl ChainWithTransactions for Polkadot {
 	) -> Result<Self::SignedTransaction, SubstrateError> {
 		let raw_payload = SignedPayload::new(
 			unsigned.call,
-			bp_polkadot::TransactionExtension::from_params(
+			bp_polkadot::SignedExtension::from_params(
 				param.spec_version,
 				param.transaction_version,
 				unsigned.era,
diff --git a/bridges/relays/client-rococo/src/lib.rs b/bridges/relays/client-rococo/src/lib.rs
index 5fad80d5c98..575660504b3 100644
--- a/bridges/relays/client-rococo/src/lib.rs
+++ b/bridges/relays/client-rococo/src/lib.rs
@@ -18,7 +18,7 @@
 
 pub mod codegen_runtime;
 
-use bp_polkadot_core::SuffixedCommonTransactionExtensionExt;
+use bp_polkadot_core::SuffixedCommonSignedExtensionExt;
 use bp_rococo::ROCOCO_SYNCED_HEADERS_GRANDPA_INFO_METHOD;
 use codec::Encode;
 use relay_substrate_client::{
@@ -84,7 +84,7 @@ impl RelayChain for Rococo {
 impl ChainWithTransactions for Rococo {
 	type AccountKeyPair = sp_core::sr25519::Pair;
 	type SignedTransaction =
-		bp_polkadot_core::UncheckedExtrinsic<Self::Call, bp_rococo::TransactionExtension>;
+		bp_polkadot_core::UncheckedExtrinsic<Self::Call, bp_rococo::SignedExtension>;
 
 	fn sign_transaction(
 		param: SignParam<Self>,
@@ -92,7 +92,7 @@ impl ChainWithTransactions for Rococo {
 	) -> Result<Self::SignedTransaction, SubstrateError> {
 		let raw_payload = SignedPayload::new(
 			unsigned.call,
-			bp_rococo::TransactionExtension::from_params(
+			bp_rococo::SignedExtension::from_params(
 				param.spec_version,
 				param.transaction_version,
 				unsigned.era,
diff --git a/bridges/relays/client-substrate/Cargo.toml b/bridges/relays/client-substrate/Cargo.toml
index ea02a0ad201..48895eff39d 100644
--- a/bridges/relays/client-substrate/Cargo.toml
+++ b/bridges/relays/client-substrate/Cargo.toml
@@ -10,7 +10,7 @@ workspace = true
 
 [dependencies]
 async-std = { version = "1.6.5", features = ["attributes"] }
-async-trait = "0.1"
+async-trait = "0.1.74"
 codec = { package = "parity-scale-codec", version = "3.1.5" }
 futures = "0.3.30"
 jsonrpsee = { version = "0.17", features = ["macros", "ws-client"] }
diff --git a/bridges/relays/client-westend/src/lib.rs b/bridges/relays/client-westend/src/lib.rs
index 737c6c08519..42206baecf8 100644
--- a/bridges/relays/client-westend/src/lib.rs
+++ b/bridges/relays/client-westend/src/lib.rs
@@ -18,7 +18,7 @@
 
 pub mod codegen_runtime;
 
-use bp_polkadot_core::SuffixedCommonTransactionExtensionExt;
+use bp_polkadot_core::SuffixedCommonSignedExtensionExt;
 use bp_westend::WESTEND_SYNCED_HEADERS_GRANDPA_INFO_METHOD;
 use codec::Encode;
 use relay_substrate_client::{
@@ -84,7 +84,7 @@ impl ChainWithBalances for Westend {
 impl ChainWithTransactions for Westend {
 	type AccountKeyPair = sp_core::sr25519::Pair;
 	type SignedTransaction =
-		bp_polkadot_core::UncheckedExtrinsic<Self::Call, bp_westend::TransactionExtension>;
+		bp_polkadot_core::UncheckedExtrinsic<Self::Call, bp_westend::SignedExtension>;
 
 	fn sign_transaction(
 		param: SignParam<Self>,
@@ -92,7 +92,7 @@ impl ChainWithTransactions for Westend {
 	) -> Result<Self::SignedTransaction, SubstrateError> {
 		let raw_payload = SignedPayload::new(
 			unsigned.call,
-			bp_westend::TransactionExtension::from_params(
+			bp_westend::SignedExtension::from_params(
 				param.spec_version,
 				param.transaction_version,
 				unsigned.era,
diff --git a/bridges/relays/equivocation/Cargo.toml b/bridges/relays/equivocation/Cargo.toml
index 0b4a7e983a3..23dd4a087f9 100644
--- a/bridges/relays/equivocation/Cargo.toml
+++ b/bridges/relays/equivocation/Cargo.toml
@@ -11,7 +11,7 @@ workspace = true
 
 [dependencies]
 async-std = { version = "1.6.5", features = ["attributes"] }
-async-trait = "0.1"
+async-trait = "0.1.74"
 bp-header-chain = { path = "../../primitives/header-chain" }
 finality-relay = { path = "../finality" }
 frame-support = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master" }
diff --git a/bridges/relays/finality/Cargo.toml b/bridges/relays/finality/Cargo.toml
index 9e8bf56f53a..80159b4db62 100644
--- a/bridges/relays/finality/Cargo.toml
+++ b/bridges/relays/finality/Cargo.toml
@@ -11,7 +11,7 @@ workspace = true
 
 [dependencies]
 async-std = "1.6.5"
-async-trait = "0.1"
+async-trait = "0.1.74"
 backoff = "0.4"
 bp-header-chain = { path = "../../primitives/header-chain" }
 futures = "0.3.30"
diff --git a/bridges/relays/lib-substrate-relay/Cargo.toml b/bridges/relays/lib-substrate-relay/Cargo.toml
index d85e2762a4e..27c5ee02c75 100644
--- a/bridges/relays/lib-substrate-relay/Cargo.toml
+++ b/bridges/relays/lib-substrate-relay/Cargo.toml
@@ -11,7 +11,7 @@ workspace = true
 [dependencies]
 anyhow = "1.0"
 async-std = "1.9.0"
-async-trait = "0.1"
+async-trait = "0.1.74"
 codec = { package = "parity-scale-codec", version = "3.1.5" }
 futures = "0.3.30"
 hex = "0.4"
diff --git a/bridges/relays/messages/Cargo.toml b/bridges/relays/messages/Cargo.toml
index 3367e4bbd44..699649dd619 100644
--- a/bridges/relays/messages/Cargo.toml
+++ b/bridges/relays/messages/Cargo.toml
@@ -10,7 +10,7 @@ workspace = true
 
 [dependencies]
 async-std = { version = "1.6.5", features = ["attributes"] }
-async-trait = "0.1"
+async-trait = "0.1.74"
 env_logger = "0.11"
 futures = "0.3.30"
 hex = "0.4"
diff --git a/bridges/relays/parachains/Cargo.toml b/bridges/relays/parachains/Cargo.toml
index 9dc35343b48..d2eea9eb6a7 100644
--- a/bridges/relays/parachains/Cargo.toml
+++ b/bridges/relays/parachains/Cargo.toml
@@ -10,7 +10,7 @@ workspace = true
 
 [dependencies]
 async-std = "1.6.5"
-async-trait = "0.1"
+async-trait = "0.1.74"
 futures = "0.3.30"
 log = { workspace = true }
 relay-utils = { path = "../utils" }
diff --git a/bridges/relays/utils/Cargo.toml b/bridges/relays/utils/Cargo.toml
index ed6093318a0..81574a68551 100644
--- a/bridges/relays/utils/Cargo.toml
+++ b/bridges/relays/utils/Cargo.toml
@@ -12,7 +12,7 @@ workspace = true
 ansi_term = "0.12"
 anyhow = "1.0"
 async-std = "1.6.5"
-async-trait = "0.1"
+async-trait = "0.1.74"
 backoff = "0.4"
 isahc = "1.2"
 env_logger = "0.11.3"
diff --git a/bridges/testing/README.md b/bridges/testing/README.md
new file mode 100644
index 00000000000..bd467a410d0
--- /dev/null
+++ b/bridges/testing/README.md
@@ -0,0 +1,31 @@
+# Bridges Tests for Local Rococo <> Westend Bridge
+
+This folder contains [zombienet](https://github.com/paritytech/zombienet/) based integration tests for both
+onchain and offchain bridges code. Due to some
+[technical difficulties](https://github.com/paritytech/parity-bridges-common/pull/2649#issue-1965339051), we
+are using native zombienet provider, which means that you need to build some binaries locally.
+
+To start those tests, you need to:
+
+- download latest [zombienet release](https://github.com/paritytech/zombienet/releases);
+
+- build Polkadot binary by running `cargo build -p polkadot --release  --features fast-runtime` command in the
+[`polkadot-sdk`](https://github.com/paritytech/polkadot-sdk) repository clone;
+
+- build Polkadot Parachain binary by running `cargo build -p polkadot-parachain-bin --release` command in the
+[`polkadot-sdk`](https://github.com/paritytech/polkadot-sdk) repository clone;
+
+- ensure that you have [`node`](https://nodejs.org/en) installed. Additionally, we'll need globally installed
+`polkadot/api-cli` package (use `npm install -g @polkadot/api-cli@beta` to install it);
+
+- build Substrate relay by running `cargo build -p substrate-relay --release` command in the
+[`parity-bridges-common`](https://github.com/paritytech/parity-bridges-common) repository clone.
+
+- copy fresh `substrate-relay` binary, built in previous point, to the `~/local_bridge_testing/bin/substrate-relay`;
+
+- change the `POLKADOT_SDK_PATH` and `ZOMBIENET_BINARY_PATH` (and ensure that the nearby variables
+have correct values) in the `./run-tests.sh`.
+
+After that, you could run tests with the `./run-tests.sh` command. Hopefully, it'll show the
+"All tests have completed successfully" message in the end. Otherwise, it'll print paths to zombienet
+process logs, which, in turn, may be used to track locations of all spinned relay and parachain nodes.
diff --git a/bridges/testing/environments/rococo-westend/bridge_hub_rococo_local_network.toml b/bridges/testing/environments/rococo-westend/bridge_hub_rococo_local_network.toml
new file mode 100644
index 00000000000..52271f94421
--- /dev/null
+++ b/bridges/testing/environments/rococo-westend/bridge_hub_rococo_local_network.toml
@@ -0,0 +1,88 @@
+[settings]
+node_spawn_timeout = 240
+
+[relaychain]
+default_command = "{{POLKADOT_BINARY}}"
+default_args = [ "-lparachain=debug,xcm=trace" ]
+chain = "rococo-local"
+
+	[[relaychain.nodes]]
+	name = "alice-rococo-validator"
+	validator = true
+	rpc_port = 9932
+	ws_port = 9942
+	balance = 2000000000000
+
+	[[relaychain.nodes]]
+	name = "bob-rococo-validator"
+	validator = true
+	rpc_port = 9933
+	ws_port = 9943
+	balance = 2000000000000
+
+	[[relaychain.nodes]]
+	name = "charlie-rococo-validator"
+	validator = true
+	rpc_port = 9934
+	ws_port = 9944
+	balance = 2000000000000
+
+[[parachains]]
+id = 1013
+chain = "bridge-hub-rococo-local"
+cumulus_based = true
+
+	# run alice as parachain collator
+	[[parachains.collators]]
+	name = "bridge-hub-rococo-collator1"
+	validator = true
+	command = "{{POLKADOT_PARACHAIN_BINARY}}"
+	rpc_port = 8933
+	ws_port = 8943
+	args = [
+		"-lparachain=debug,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm=trace"
+	]
+
+	# run bob as parachain collator
+	[[parachains.collators]]
+	name = "bridge-hub-rococo-collator2"
+	validator = true
+	command = "{{POLKADOT_PARACHAIN_BINARY}}"
+	rpc_port = 8934
+	ws_port = 8944
+	args = [
+		"-lparachain=trace,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm=trace"
+	]
+
+[[parachains]]
+id = 1000
+chain = "asset-hub-rococo-local"
+cumulus_based = true
+
+	[[parachains.collators]]
+	name = "asset-hub-rococo-collator1"
+	rpc_port = 9911
+	ws_port = 9910
+	command = "{{POLKADOT_PARACHAIN_BINARY}}"
+	args = [
+		"-lparachain=debug,xcm=trace,runtime::bridge-transfer=trace"
+	]
+
+	[[parachains.collators]]
+	name = "asset-hub-rococo-collator2"
+	command = "{{POLKADOT_PARACHAIN_BINARY}}"
+	args = [
+		"-lparachain=debug,xcm=trace,runtime::bridge-transfer=trace"
+	]
+
+#[[hrmp_channels]]
+#sender = 1000
+#recipient = 1013
+#max_capacity = 4
+#max_message_size = 524288
+#
+#[[hrmp_channels]]
+#sender = 1013
+#recipient = 1000
+#max_capacity = 4
+#max_message_size = 524288
diff --git a/bridges/testing/environments/rococo-westend/bridge_hub_westend_local_network.toml b/bridges/testing/environments/rococo-westend/bridge_hub_westend_local_network.toml
new file mode 100644
index 00000000000..f2550bcc995
--- /dev/null
+++ b/bridges/testing/environments/rococo-westend/bridge_hub_westend_local_network.toml
@@ -0,0 +1,88 @@
+[settings]
+node_spawn_timeout = 240
+
+[relaychain]
+default_command = "{{POLKADOT_BINARY}}"
+default_args = [ "-lparachain=debug,xcm=trace" ]
+chain = "westend-local"
+
+	[[relaychain.nodes]]
+	name = "alice-westend-validator"
+	validator = true
+	rpc_port = 9935
+	ws_port = 9945
+	balance = 2000000000000
+
+	[[relaychain.nodes]]
+	name = "bob-westend-validator"
+	validator = true
+	rpc_port = 9936
+	ws_port = 9946
+	balance = 2000000000000
+
+	[[relaychain.nodes]]
+	name = "charlie-westend-validator"
+	validator = true
+	rpc_port = 9937
+	ws_port = 9947
+	balance = 2000000000000
+
+[[parachains]]
+id = 1002
+chain = "bridge-hub-westend-local"
+cumulus_based = true
+
+	# run alice as parachain collator
+	[[parachains.collators]]
+	name = "bridge-hub-westend-collator1"
+	validator = true
+	command = "{{POLKADOT_PARACHAIN_BINARY}}"
+	rpc_port = 8935
+	ws_port = 8945
+	args = [
+		"-lparachain=debug,runtime::mmr=info,substrate=info,runtime=info,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm=trace"
+	]
+
+	# run bob as parachain collator
+	[[parachains.collators]]
+	name = "bridge-hub-westend-collator2"
+	validator = true
+	command = "{{POLKADOT_PARACHAIN_BINARY}}"
+	rpc_port = 8936
+	ws_port = 8946
+	args = [
+		"-lparachain=trace,runtime::mmr=info,substrate=info,runtime=info,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm=trace"
+	]
+
+[[parachains]]
+id = 1000
+chain = "asset-hub-westend-local"
+cumulus_based = true
+
+	[[parachains.collators]]
+	name = "asset-hub-westend-collator1"
+	rpc_port = 9011
+	ws_port = 9010
+	command = "{{POLKADOT_PARACHAIN_BINARY}}"
+	args = [
+		"-lparachain=debug,xcm=trace,runtime::bridge-transfer=trace"
+	]
+
+	[[parachains.collators]]
+	name = "asset-hub-westend-collator2"
+	command = "{{POLKADOT_PARACHAIN_BINARY}}"
+	args = [
+		"-lparachain=debug,xcm=trace,runtime::bridge-transfer=trace"
+	]
+
+#[[hrmp_channels]]
+#sender = 1000
+#recipient = 1002
+#max_capacity = 4
+#max_message_size = 524288
+#
+#[[hrmp_channels]]
+#sender = 1002
+#recipient = 1000
+#max_capacity = 4
+#max_message_size = 524288
diff --git a/bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh b/bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh
new file mode 100755
index 00000000000..66c9ddc037b
--- /dev/null
+++ b/bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh
@@ -0,0 +1,401 @@
+#!/bin/bash
+
+# import common functions
+source "$FRAMEWORK_PATH/utils/bridges.sh"
+
+# Expected sovereign accounts.
+#
+# Generated by:
+#
+#  #[test]
+#	fn generate_sovereign_accounts() {
+#		use sp_core::crypto::Ss58Codec;
+#		use polkadot_parachain_primitives::primitives::Sibling;
+#
+#		parameter_types! {
+#			pub UniversalLocationAHR: InteriorMultiLocation = X2(GlobalConsensus(Rococo), Parachain(1000));
+#			pub UniversalLocationAHW: InteriorMultiLocation = X2(GlobalConsensus(Westend), Parachain(1000));
+#		}
+#
+#		// SS58=42
+#		println!("GLOBAL_CONSENSUS_ROCOCO_SOVEREIGN_ACCOUNT=\"{}\"",
+#				 frame_support::sp_runtime::AccountId32::new(
+#					 GlobalConsensusConvertsFor::<UniversalLocationAHW, [u8; 32]>::convert_location(
+#						 &MultiLocation { parents: 2, interior: X1(GlobalConsensus(Rococo)) }).unwrap()
+#				 ).to_ss58check_with_version(42_u16.into())
+#		);
+#		println!("ASSET_HUB_WESTEND_SOVEREIGN_ACCOUNT_AT_BRIDGE_HUB_WESTEND=\"{}\"",
+#				 frame_support::sp_runtime::AccountId32::new(
+#					 SiblingParachainConvertsVia::<Sibling, [u8; 32]>::convert_location(
+#						 &MultiLocation { parents: 1, interior: X1(Parachain(1000)) }).unwrap()
+#				 ).to_ss58check_with_version(42_u16.into())
+#		);
+#
+#		// SS58=42
+#		println!("GLOBAL_CONSENSUS_WESTEND_SOVEREIGN_ACCOUNT=\"{}\"",
+#				 frame_support::sp_runtime::AccountId32::new(
+#					 GlobalConsensusConvertsFor::<UniversalLocationAHR, [u8; 32]>::convert_location(
+#						 &MultiLocation { parents: 2, interior: X1(GlobalConsensus(Westend)) }).unwrap()
+#				 ).to_ss58check_with_version(42_u16.into())
+#		);
+#		println!("ASSET_HUB_ROCOCO_SOVEREIGN_ACCOUNT_AT_BRIDGE_HUB_ROCOCO=\"{}\"",
+#				 frame_support::sp_runtime::AccountId32::new(
+#					 SiblingParachainConvertsVia::<Sibling, [u8; 32]>::convert_location(
+#						 &MultiLocation { parents: 1, interior: X1(Parachain(1000)) }).unwrap()
+#				 ).to_ss58check_with_version(42_u16.into())
+#		);
+#	}
+GLOBAL_CONSENSUS_ROCOCO_SOVEREIGN_ACCOUNT="5GxRGwT8bU1JeBPTUXc7LEjZMxNrK8MyL2NJnkWFQJTQ4sii"
+ASSET_HUB_WESTEND_SOVEREIGN_ACCOUNT_AT_BRIDGE_HUB_WESTEND="5Eg2fntNprdN3FgH4sfEaaZhYtddZQSQUqvYJ1f2mLtinVhV"
+GLOBAL_CONSENSUS_WESTEND_SOVEREIGN_ACCOUNT="5He2Qdztyxxa4GoagY6q1jaiLMmKy1gXS7PdZkhfj8ZG9hk5"
+ASSET_HUB_ROCOCO_SOVEREIGN_ACCOUNT_AT_BRIDGE_HUB_ROCOCO="5Eg2fntNprdN3FgH4sfEaaZhYtddZQSQUqvYJ1f2mLtinVhV"
+
+# Expected sovereign accounts for rewards on BridgeHubs.
+#
+# Generated by:
+#	#[test]
+#	fn generate_sovereign_accounts_for_rewards() {
+#		use bp_messages::LaneId;
+#		use bp_relayers::{PayRewardFromAccount, RewardsAccountOwner, RewardsAccountParams};
+#		use sp_core::crypto::Ss58Codec;
+#
+#		// SS58=42
+#		println!(
+#			"ON_BRIDGE_HUB_ROCOCO_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhwd_ThisChain=\"{}\"",
+#			frame_support::sp_runtime::AccountId32::new(
+#				PayRewardFromAccount::<[u8; 32], [u8; 32]>::rewards_account(RewardsAccountParams::new(
+#					LaneId([0, 0, 0, 2]),
+#					*b"bhwd",
+#					RewardsAccountOwner::ThisChain
+#				))
+#			)
+#				.to_ss58check_with_version(42_u16.into())
+#		);
+#		// SS58=42
+#		println!(
+#			"ON_BRIDGE_HUB_ROCOCO_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhwd_BridgedChain=\"{}\"",
+#			frame_support::sp_runtime::AccountId32::new(
+#				PayRewardFromAccount::<[u8; 32], [u8; 32]>::rewards_account(RewardsAccountParams::new(
+#					LaneId([0, 0, 0, 2]),
+#					*b"bhwd",
+#					RewardsAccountOwner::BridgedChain
+#				))
+#			)
+#				.to_ss58check_with_version(42_u16.into())
+#		);
+#
+#		// SS58=42
+#		println!(
+#			"ON_BRIDGE_HUB_WESTEND_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhro_ThisChain=\"{}\"",
+#			frame_support::sp_runtime::AccountId32::new(
+#				PayRewardFromAccount::<[u8; 32], [u8; 32]>::rewards_account(RewardsAccountParams::new(
+#					LaneId([0, 0, 0, 2]),
+#					*b"bhro",
+#					RewardsAccountOwner::ThisChain
+#				))
+#			)
+#				.to_ss58check_with_version(42_u16.into())
+#		);
+#		// SS58=42
+#		println!(
+#			"ON_BRIDGE_HUB_WESTEND_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhro_BridgedChain=\"{}\"",
+#			frame_support::sp_runtime::AccountId32::new(
+#				PayRewardFromAccount::<[u8; 32], [u8; 32]>::rewards_account(RewardsAccountParams::new(
+#					LaneId([0, 0, 0, 2]),
+#					*b"bhro",
+#					RewardsAccountOwner::BridgedChain
+#				))
+#			)
+#				.to_ss58check_with_version(42_u16.into())
+#		);
+#	}
+ON_BRIDGE_HUB_ROCOCO_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhwd_ThisChain="5EHnXaT5BhiSGP5hbdsoVGtzi2sQVgpDNToTxLYeQvKoMPEm"
+ON_BRIDGE_HUB_ROCOCO_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhwd_BridgedChain="5EHnXaT5BhiSGP5hbdt5EJSapXYbxEv678jyWHEUskCXcjqo"
+ON_BRIDGE_HUB_WESTEND_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhro_ThisChain="5EHnXaT5BhiSGP5h9Rg8sgUJqoLym3iEaWUiboT8S9AT5xFh"
+ON_BRIDGE_HUB_WESTEND_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhro_BridgedChain="5EHnXaT5BhiSGP5h9RgQci1txJ2BDbp7KBRE9k8xty3BMUSi"
+
+LANE_ID="00000002"
+XCM_VERSION=3
+
+function init_ro_wnd() {
+    local relayer_path=$(ensure_relayer)
+
+    RUST_LOG=runtime=trace,rpc=trace,bridge=trace \
+        $relayer_path init-bridge rococo-to-bridge-hub-westend \
+	--source-host localhost \
+	--source-port 9942 \
+	--source-version-mode Auto \
+	--target-host localhost \
+	--target-port 8945 \
+	--target-version-mode Auto \
+	--target-signer //Bob
+}
+
+function init_wnd_ro() {
+    local relayer_path=$(ensure_relayer)
+
+    RUST_LOG=runtime=trace,rpc=trace,bridge=trace \
+        $relayer_path init-bridge westend-to-bridge-hub-rococo \
+        --source-host localhost \
+        --source-port 9945 \
+        --source-version-mode Auto \
+        --target-host localhost \
+        --target-port 8943 \
+        --target-version-mode Auto \
+        --target-signer //Bob
+}
+
+function run_relay() {
+    local relayer_path=$(ensure_relayer)
+
+    RUST_LOG=runtime=trace,rpc=trace,bridge=trace \
+        $relayer_path relay-headers-and-messages bridge-hub-rococo-bridge-hub-westend \
+        --rococo-host localhost \
+        --rococo-port 9942 \
+        --rococo-version-mode Auto \
+        --bridge-hub-rococo-host localhost \
+        --bridge-hub-rococo-port 8943 \
+        --bridge-hub-rococo-version-mode Auto \
+        --bridge-hub-rococo-signer //Charlie \
+        --bridge-hub-rococo-transactions-mortality 4 \
+        --westend-host localhost \
+        --westend-port 9945 \
+        --westend-version-mode Auto \
+        --bridge-hub-westend-host localhost \
+        --bridge-hub-westend-port 8945 \
+        --bridge-hub-westend-version-mode Auto \
+        --bridge-hub-westend-signer //Charlie \
+        --bridge-hub-westend-transactions-mortality 4 \
+        --lane "${LANE_ID}"
+}
+
+case "$1" in
+  run-relay)
+    init_wnd_ro
+    init_ro_wnd
+    run_relay
+    ;;
+  init-asset-hub-rococo-local)
+      ensure_polkadot_js_api
+      # create foreign assets for native Westend token (governance call on Rococo)
+      force_create_foreign_asset \
+          "ws://127.0.0.1:9942" \
+          "//Alice" \
+          1000 \
+          "ws://127.0.0.1:9910" \
+          "$(jq --null-input '{ "parents": 2, "interior": { "X1": { "GlobalConsensus": "Westend" } } }')" \
+          "$GLOBAL_CONSENSUS_WESTEND_SOVEREIGN_ACCOUNT" \
+          10000000000 \
+          true
+      # HRMP
+      open_hrmp_channels \
+          "ws://127.0.0.1:9942" \
+          "//Alice" \
+          1000 1013 4 524288
+      open_hrmp_channels \
+          "ws://127.0.0.1:9942" \
+          "//Alice" \
+          1013 1000 4 524288
+      # set XCM version of remote AssetHubWestend
+      force_xcm_version \
+          "ws://127.0.0.1:9942" \
+          "//Alice" \
+          1000 \
+          "ws://127.0.0.1:9910" \
+          "$(jq --null-input '{ "parents": 2, "interior": { "X2": [ { "GlobalConsensus": "Westend" }, { "Parachain": 1000 } ] } }')" \
+          $XCM_VERSION
+      ;;
+  init-bridge-hub-rococo-local)
+      ensure_polkadot_js_api
+      # SA of sibling asset hub pays for the execution
+      transfer_balance \
+          "ws://127.0.0.1:8943" \
+          "//Alice" \
+          "$ASSET_HUB_ROCOCO_SOVEREIGN_ACCOUNT_AT_BRIDGE_HUB_ROCOCO" \
+          $((1000000000000 + 50000000000 * 20))
+      # drip SA of lane dedicated to asset hub for paying rewards for delivery
+      transfer_balance \
+          "ws://127.0.0.1:8943" \
+          "//Alice" \
+          "$ON_BRIDGE_HUB_ROCOCO_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhwd_ThisChain" \
+          $((1000000000000 + 2000000000000))
+      # drip SA of lane dedicated to asset hub for paying rewards for delivery confirmation
+      transfer_balance \
+          "ws://127.0.0.1:8943" \
+          "//Alice" \
+          "$ON_BRIDGE_HUB_ROCOCO_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhwd_BridgedChain" \
+          $((1000000000000 + 2000000000000))
+      # set XCM version of remote BridgeHubWestend
+      force_xcm_version \
+          "ws://127.0.0.1:9942" \
+          "//Alice" \
+          1013 \
+          "ws://127.0.0.1:8943" \
+          "$(jq --null-input '{ "parents": 2, "interior": { "X2": [ { "GlobalConsensus": "Westend" }, { "Parachain": 1002 } ] } }')" \
+          $XCM_VERSION
+      ;;
+  init-asset-hub-westend-local)
+      ensure_polkadot_js_api
+      # create foreign assets for native Rococo token (governance call on Westend)
+      force_create_foreign_asset \
+          "ws://127.0.0.1:9945" \
+          "//Alice" \
+          1000 \
+          "ws://127.0.0.1:9010" \
+          "$(jq --null-input '{ "parents": 2, "interior": { "X1": { "GlobalConsensus": "Rococo" } } }')" \
+          "$GLOBAL_CONSENSUS_ROCOCO_SOVEREIGN_ACCOUNT" \
+          10000000000 \
+          true
+      # HRMP
+      open_hrmp_channels \
+          "ws://127.0.0.1:9945" \
+          "//Alice" \
+          1000 1002 4 524288
+      open_hrmp_channels \
+          "ws://127.0.0.1:9945" \
+          "//Alice" \
+          1002 1000 4 524288
+      # set XCM version of remote AssetHubRococo
+      force_xcm_version \
+          "ws://127.0.0.1:9945" \
+          "//Alice" \
+          1000 \
+          "ws://127.0.0.1:9010" \
+          "$(jq --null-input '{ "parents": 2, "interior": { "X2": [ { "GlobalConsensus": "Rococo" }, { "Parachain": 1000 } ] } }')" \
+          $XCM_VERSION
+      ;;
+  init-bridge-hub-westend-local)
+      # SA of sibling asset hub pays for the execution
+      transfer_balance \
+          "ws://127.0.0.1:8945" \
+          "//Alice" \
+          "$ASSET_HUB_WESTEND_SOVEREIGN_ACCOUNT_AT_BRIDGE_HUB_WESTEND" \
+          $((1000000000000000 + 50000000000 * 20))
+      # drip SA of lane dedicated to asset hub for paying rewards for delivery
+      transfer_balance \
+          "ws://127.0.0.1:8945" \
+          "//Alice" \
+          "$ON_BRIDGE_HUB_WESTEND_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhro_ThisChain" \
+          $((1000000000000000 + 2000000000000))
+      # drip SA of lane dedicated to asset hub for paying rewards for delivery confirmation
+      transfer_balance \
+          "ws://127.0.0.1:8945" \
+          "//Alice" \
+          "$ON_BRIDGE_HUB_WESTEND_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhro_BridgedChain" \
+          $((1000000000000000 + 2000000000000))
+      # set XCM version of remote BridgeHubRococo
+      force_xcm_version \
+          "ws://127.0.0.1:9945" \
+          "//Alice" \
+          1002 \
+          "ws://127.0.0.1:8945" \
+          "$(jq --null-input '{ "parents": 2, "interior": { "X2": [ { "GlobalConsensus": "Rococo" }, { "Parachain": 1013 } ] } }')" \
+          $XCM_VERSION
+      ;;
+  reserve-transfer-assets-from-asset-hub-rococo-local)
+      amount=$2
+      ensure_polkadot_js_api
+      # send ROCs to Alice account on AHW
+      limited_reserve_transfer_assets \
+          "ws://127.0.0.1:9910" \
+          "//Alice" \
+          "$(jq --null-input '{ "V3": { "parents": 2, "interior": { "X2": [ { "GlobalConsensus": "Westend" }, { "Parachain": 1000 } ] } } }')" \
+          "$(jq --null-input '{ "V3": { "parents": 0, "interior": { "X1": { "AccountId32": { "id": [212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, 133, 88, 133, 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125] } } } } }')" \
+          "$(jq --null-input '{ "V3": [ { "id": { "Concrete": { "parents": 1, "interior": "Here" } }, "fun": { "Fungible": '$amount' } } ] }')" \
+          0 \
+          "Unlimited"
+      ;;
+  withdraw-reserve-assets-from-asset-hub-rococo-local)
+      amount=$2
+      ensure_polkadot_js_api
+      # send back only 100000000000 wrappedWNDs to Alice account on AHW
+      limited_reserve_transfer_assets \
+          "ws://127.0.0.1:9910" \
+          "//Alice" \
+          "$(jq --null-input '{ "V3": { "parents": 2, "interior": { "X2": [ { "GlobalConsensus": "Westend" }, { "Parachain": 1000 } ] } } }')" \
+          "$(jq --null-input '{ "V3": { "parents": 0, "interior": { "X1": { "AccountId32": { "id": [212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, 133, 88, 133, 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125] } } } } }')" \
+          "$(jq --null-input '{ "V3": [ { "id": { "Concrete": { "parents": 2, "interior": { "X1": { "GlobalConsensus": "Westend" } } } }, "fun": { "Fungible": '$amount' } } ] }')" \
+          0 \
+          "Unlimited"
+      ;;
+  reserve-transfer-assets-from-asset-hub-westend-local)
+      amount=$2
+      ensure_polkadot_js_api
+      # send WNDs to Alice account on AHR
+      limited_reserve_transfer_assets \
+          "ws://127.0.0.1:9010" \
+          "//Alice" \
+          "$(jq --null-input '{ "V3": { "parents": 2, "interior": { "X2": [ { "GlobalConsensus": "Rococo" }, { "Parachain": 1000 } ] } } }')" \
+          "$(jq --null-input '{ "V3": { "parents": 0, "interior": { "X1": { "AccountId32": { "id": [212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, 133, 88, 133, 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125] } } } } }')" \
+          "$(jq --null-input '{ "V3": [ { "id": { "Concrete": { "parents": 1, "interior": "Here" } }, "fun": { "Fungible": '$amount' } } ] }')" \
+          0 \
+          "Unlimited"
+      ;;
+  withdraw-reserve-assets-from-asset-hub-westend-local)
+      amount=$2
+      ensure_polkadot_js_api
+      # send back only 100000000000 wrappedROCs to Alice account on AHR
+      limited_reserve_transfer_assets \
+          "ws://127.0.0.1:9010" \
+          "//Alice" \
+          "$(jq --null-input '{ "V3": { "parents": 2, "interior": { "X2": [ { "GlobalConsensus": "Rococo" }, { "Parachain": 1000 } ] } } }')" \
+          "$(jq --null-input '{ "V3": { "parents": 0, "interior": { "X1": { "AccountId32": { "id": [212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, 133, 88, 133, 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125] } } } } }')" \
+          "$(jq --null-input '{ "V3": [ { "id": { "Concrete": { "parents": 2, "interior": { "X1": { "GlobalConsensus": "Rococo" } } } }, "fun": { "Fungible": '$amount' } } ] }')" \
+          0 \
+          "Unlimited"
+      ;;
+  claim-rewards-bridge-hub-rococo-local)
+      ensure_polkadot_js_api
+      # bhwd -> [62, 68, 77, 64] -> 0x62687764
+      claim_rewards \
+          "ws://127.0.0.1:8943" \
+          "//Charlie" \
+          "0x${LANE_ID}" \
+          "0x62687764" \
+          "ThisChain"
+      claim_rewards \
+          "ws://127.0.0.1:8943" \
+          "//Charlie" \
+          "0x${LANE_ID}" \
+          "0x62687764" \
+          "BridgedChain"
+      ;;
+  claim-rewards-bridge-hub-westend-local)
+      # bhro -> [62, 68, 72, 6f] -> 0x6268726f
+      claim_rewards \
+          "ws://127.0.0.1:8945" \
+          "//Charlie" \
+          "0x${LANE_ID}" \
+          "0x6268726f" \
+          "ThisChain"
+      claim_rewards \
+          "ws://127.0.0.1:8945" \
+          "//Charlie" \
+          "0x${LANE_ID}" \
+          "0x6268726f" \
+          "BridgedChain"
+      ;;
+  stop)
+    pkill -f polkadot
+    pkill -f parachain
+    ;;
+  import)
+    # to avoid trigger anything here
+    ;;
+  *)
+    echo "A command is require. Supported commands for:
+    Local (zombienet) run:
+          - run-relay
+          - init-asset-hub-rococo-local
+          - init-bridge-hub-rococo-local
+          - init-asset-hub-westend-local
+          - init-bridge-hub-westend-local
+          - reserve-transfer-assets-from-asset-hub-rococo-local
+          - withdraw-reserve-assets-from-asset-hub-rococo-local
+          - reserve-transfer-assets-from-asset-hub-westend-local
+          - withdraw-reserve-assets-from-asset-hub-westend-local
+          - claim-rewards-bridge-hub-rococo-local
+          - claim-rewards-bridge-hub-westend-local";
+    exit 1
+    ;;
+esac
diff --git a/bridges/testing/environments/rococo-westend/helper.sh b/bridges/testing/environments/rococo-westend/helper.sh
new file mode 100755
index 00000000000..0a13ded213f
--- /dev/null
+++ b/bridges/testing/environments/rococo-westend/helper.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+$ENV_PATH/bridges_rococo_westend.sh "$@"
diff --git a/bridges/testing/environments/rococo-westend/rococo-init.zndsl b/bridges/testing/environments/rococo-westend/rococo-init.zndsl
new file mode 100644
index 00000000000..c913e4db31f
--- /dev/null
+++ b/bridges/testing/environments/rococo-westend/rococo-init.zndsl
@@ -0,0 +1,8 @@
+Description: Check if the HRMP channel between Rococo BH and Rococo AH was opened successfully
+Network: ./bridge_hub_rococo_local_network.toml
+Creds: config
+
+# ensure that initialization has completed
+asset-hub-rococo-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/wait-hrmp-channel-opened.js with "1013" within 300 seconds
+
+
diff --git a/bridges/testing/environments/rococo-westend/rococo.zndsl b/bridges/testing/environments/rococo-westend/rococo.zndsl
new file mode 100644
index 00000000000..5b49c7c632f
--- /dev/null
+++ b/bridges/testing/environments/rococo-westend/rococo.zndsl
@@ -0,0 +1,7 @@
+Description: Check if the with-Westend GRANPDA pallet was initialized at Rococo BH
+Network: ./bridge_hub_rococo_local_network.toml
+Creds: config
+
+# relay is already started - let's wait until with-Westend GRANPDA pallet is initialized at Rococo
+bridge-hub-rococo-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/best-finalized-header-at-bridged-chain.js with "Westend,0" within 400 seconds
+
diff --git a/bridges/testing/environments/rococo-westend/spawn.sh b/bridges/testing/environments/rococo-westend/spawn.sh
new file mode 100755
index 00000000000..cbd0b1bc623
--- /dev/null
+++ b/bridges/testing/environments/rococo-westend/spawn.sh
@@ -0,0 +1,70 @@
+#!/bin/bash
+
+set -e
+
+trap "trap - SIGTERM && kill -9 -$$" SIGINT SIGTERM EXIT
+
+source "$FRAMEWORK_PATH/utils/zombienet.sh"
+
+# whether to init the chains (open HRMP channels, set XCM version, create reserve assets, etc)
+init=0
+start_relayer=0
+while [ $# -ne 0 ]
+do
+    arg="$1"
+    case "$arg" in
+        --init)
+            init=1
+            ;;
+        --start-relayer)
+            start_relayer=1
+            ;;
+    esac
+    shift
+done
+
+logs_dir=$TEST_DIR/logs
+helper_script="${BASH_SOURCE%/*}/helper.sh"
+
+rococo_def=${BASH_SOURCE%/*}/bridge_hub_rococo_local_network.toml
+start_zombienet $TEST_DIR $rococo_def rococo_dir rococo_pid
+echo
+
+westend_def=${BASH_SOURCE%/*}/bridge_hub_westend_local_network.toml
+start_zombienet $TEST_DIR $westend_def westend_dir westend_pid
+echo
+
+if [[ $init -eq 1 ]]; then
+  rococo_init_log=$logs_dir/rococo-init.log
+  echo -e "Setting up the rococo side of the bridge. Logs available at: $rococo_init_log\n"
+
+  westend_init_log=$logs_dir/westend-init.log
+  echo -e "Setting up the westend side of the bridge. Logs available at: $westend_init_log\n"
+
+  $helper_script init-asset-hub-rococo-local >> $rococo_init_log 2>&1 &
+  rococo_init_pid=$!
+  $helper_script init-asset-hub-westend-local >> $westend_init_log 2>&1 &
+  westend_init_pid=$!
+  wait -n $rococo_init_pid $westend_init_pid
+
+
+  $helper_script init-bridge-hub-rococo-local >> $rococo_init_log 2>&1 &
+  rococo_init_pid=$!
+  $helper_script init-bridge-hub-westend-local >> $westend_init_log 2>&1 &
+  westend_init_pid=$!
+  wait -n $rococo_init_pid $westend_init_pid
+
+  run_zndsl ${BASH_SOURCE%/*}/rococo-init.zndsl $rococo_dir
+  run_zndsl ${BASH_SOURCE%/*}/westend-init.zndsl $westend_dir
+fi
+
+if [[ $start_relayer -eq 1 ]]; then
+  ${BASH_SOURCE%/*}/start_relayer.sh $rococo_dir $westend_dir relayer_pid
+fi
+
+echo $rococo_dir > $TEST_DIR/rococo.env
+echo $westend_dir > $TEST_DIR/westend.env
+echo
+
+wait -n $rococo_pid $westend_pid $relayer_pid
+kill -9 -$$
diff --git a/bridges/testing/environments/rococo-westend/start_relayer.sh b/bridges/testing/environments/rococo-westend/start_relayer.sh
new file mode 100755
index 00000000000..7ddd312d395
--- /dev/null
+++ b/bridges/testing/environments/rococo-westend/start_relayer.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+
+set -e
+
+source "$FRAMEWORK_PATH/utils/common.sh"
+source "$FRAMEWORK_PATH/utils/zombienet.sh"
+
+rococo_dir=$1
+westend_dir=$2
+__relayer_pid=$3
+
+logs_dir=$TEST_DIR/logs
+helper_script="${BASH_SOURCE%/*}/helper.sh"
+
+relayer_log=$logs_dir/relayer.log
+echo -e "Starting rococo-westend relayer. Logs available at: $relayer_log\n"
+start_background_process "$helper_script run-relay" $relayer_log relayer_pid
+
+run_zndsl ${BASH_SOURCE%/*}/rococo.zndsl $rococo_dir
+run_zndsl ${BASH_SOURCE%/*}/westend.zndsl $westend_dir
+
+eval $__relayer_pid="'$relayer_pid'"
+
diff --git a/bridges/testing/environments/rococo-westend/westend-init.zndsl b/bridges/testing/environments/rococo-westend/westend-init.zndsl
new file mode 100644
index 00000000000..0f5428eed3b
--- /dev/null
+++ b/bridges/testing/environments/rococo-westend/westend-init.zndsl
@@ -0,0 +1,7 @@
+Description: Check if the HRMP channel between Westend BH and Westend AH was opened successfully
+Network: ./bridge_hub_westend_local_network.toml
+Creds: config
+
+# ensure that initialization has completed
+asset-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/wait-hrmp-channel-opened.js with "1002" within 600 seconds
+
diff --git a/bridges/testing/environments/rococo-westend/westend.zndsl b/bridges/testing/environments/rococo-westend/westend.zndsl
new file mode 100644
index 00000000000..07968838852
--- /dev/null
+++ b/bridges/testing/environments/rococo-westend/westend.zndsl
@@ -0,0 +1,6 @@
+Description: Check if the with-Rococo GRANPDA pallet was initialized at Westend BH
+Network: ./bridge_hub_westend_local_network.toml
+Creds: config
+
+# relay is already started - let's wait until with-Rococo GRANPDA pallet is initialized at Westend
+bridge-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/best-finalized-header-at-bridged-chain.js with "Rococo,0" within 400 seconds
diff --git a/bridges/testing/framework/js-helpers/best-finalized-header-at-bridged-chain.js b/bridges/testing/framework/js-helpers/best-finalized-header-at-bridged-chain.js
new file mode 100644
index 00000000000..af4f18aee9b
--- /dev/null
+++ b/bridges/testing/framework/js-helpers/best-finalized-header-at-bridged-chain.js
@@ -0,0 +1,25 @@
+async function run(nodeName, networkInfo, args) {
+    const {wsUri, userDefinedTypes} = networkInfo.nodesByName[nodeName];
+    const api = await zombie.connect(wsUri, userDefinedTypes);
+
+    // TODO: could be replaced with https://github.com/polkadot-js/api/issues/4930 (depends on metadata v15) later
+    const bridgedChainName = args[0];
+    const expectedBridgedChainHeaderNumber = Number(args[1]);
+    const runtimeApiMethod = bridgedChainName + "FinalityApi_best_finalized";
+
+    while (true) {
+        const encodedBestFinalizedHeaderId = await api.rpc.state.call(runtimeApiMethod, []);
+        const bestFinalizedHeaderId = api.createType("Option<BpRuntimeHeaderId>", encodedBestFinalizedHeaderId);
+        if (bestFinalizedHeaderId.isSome) {
+            const bestFinalizedHeaderNumber = Number(bestFinalizedHeaderId.unwrap().toHuman()[0]);
+            if (bestFinalizedHeaderNumber > expectedBridgedChainHeaderNumber) {
+                return bestFinalizedHeaderNumber;
+            }
+        }
+
+        // else sleep and retry
+        await new Promise((resolve) => setTimeout(resolve, 6000));
+    }
+}
+
+module.exports = { run }
diff --git a/bridges/testing/framework/js-helpers/chains/rococo-at-westend.js b/bridges/testing/framework/js-helpers/chains/rococo-at-westend.js
new file mode 100644
index 00000000000..bcce3b3a303
--- /dev/null
+++ b/bridges/testing/framework/js-helpers/chains/rococo-at-westend.js
@@ -0,0 +1,6 @@
+module.exports = {
+    grandpaPalletName: "bridgeRococoGrandpa",
+    parachainsPalletName: "bridgeRococoParachains",
+    messagesPalletName: "bridgeRococoMessages",
+    bridgedBridgeHubParaId: 1013,
+}
diff --git a/bridges/testing/framework/js-helpers/chains/westend-at-rococo.js b/bridges/testing/framework/js-helpers/chains/westend-at-rococo.js
new file mode 100644
index 00000000000..6a15b64a23b
--- /dev/null
+++ b/bridges/testing/framework/js-helpers/chains/westend-at-rococo.js
@@ -0,0 +1,6 @@
+module.exports = {
+    grandpaPalletName: "bridgeWestendGrandpa",
+    parachainsPalletName: "bridgeWestendParachains",
+    messagesPalletName: "bridgeWestendMessages",
+    bridgedBridgeHubParaId: 1002,
+}
diff --git a/bridges/testing/framework/js-helpers/native-assets-balance-increased.js b/bridges/testing/framework/js-helpers/native-assets-balance-increased.js
new file mode 100644
index 00000000000..749c3e2fec3
--- /dev/null
+++ b/bridges/testing/framework/js-helpers/native-assets-balance-increased.js
@@ -0,0 +1,21 @@
+async function run(nodeName, networkInfo, args) {
+    const {wsUri, userDefinedTypes} = networkInfo.nodesByName[nodeName];
+    const api = await zombie.connect(wsUri, userDefinedTypes);
+
+    const accountAddress = args[0];
+    const expectedIncrease = BigInt(args[1]);
+    const initialAccountData = await api.query.system.account(accountAddress);
+    const initialAccountBalance = initialAccountData.data['free'];
+    while (true) {
+        const accountData = await api.query.system.account(accountAddress);
+        const accountBalance = accountData.data['free'];
+        if (accountBalance > initialAccountBalance + expectedIncrease) {
+            return accountBalance;
+        }
+
+        // else sleep and retry
+        await new Promise((resolve) => setTimeout(resolve, 6000));
+    }
+}
+
+module.exports = {run}
diff --git a/bridges/testing/framework/js-helpers/only-mandatory-headers-synced-when-idle.js b/bridges/testing/framework/js-helpers/only-mandatory-headers-synced-when-idle.js
new file mode 100644
index 00000000000..979179245eb
--- /dev/null
+++ b/bridges/testing/framework/js-helpers/only-mandatory-headers-synced-when-idle.js
@@ -0,0 +1,44 @@
+const utils = require("./utils");
+
+async function run(nodeName, networkInfo, args) {
+    const {wsUri, userDefinedTypes} = networkInfo.nodesByName[nodeName];
+    const api = await zombie.connect(wsUri, userDefinedTypes);
+
+    // parse arguments
+    const exitAfterSeconds = Number(args[0]);
+    const bridgedChain = require("./chains/" + args[1]);
+
+    // start listening to new blocks
+    let totalGrandpaHeaders = 0;
+    let initialParachainHeaderImported = false;
+    api.rpc.chain.subscribeNewHeads(async function (header) {
+        const apiAtParent = await api.at(header.parentHash);
+        const apiAtCurrent = await api.at(header.hash);
+        const currentEvents = await apiAtCurrent.query.system.events();
+
+        totalGrandpaHeaders += await utils.ensureOnlyMandatoryGrandpaHeadersImported(
+            bridgedChain,
+            apiAtParent,
+            apiAtCurrent,
+            currentEvents,
+        );
+        initialParachainHeaderImported = await utils.ensureOnlyInitialParachainHeaderImported(
+            bridgedChain,
+            apiAtParent,
+            apiAtCurrent,
+            currentEvents,
+        );
+    });
+
+    // wait given time
+    await new Promise(resolve => setTimeout(resolve, exitAfterSeconds * 1000));
+    // if we haven't seen any new GRANDPA or parachain headers => fail
+    if (totalGrandpaHeaders == 0) {
+        throw new Error("No bridged relay chain headers imported");
+    }
+    if (!initialParachainHeaderImported) {
+        throw new Error("No bridged parachain headers imported");
+    }
+}
+
+module.exports = { run }
diff --git a/bridges/testing/framework/js-helpers/only-required-headers-synced-when-idle.js b/bridges/testing/framework/js-helpers/only-required-headers-synced-when-idle.js
new file mode 100644
index 00000000000..8c3130e4fd9
--- /dev/null
+++ b/bridges/testing/framework/js-helpers/only-required-headers-synced-when-idle.js
@@ -0,0 +1,81 @@
+const utils = require("./utils");
+
+async function run(nodeName, networkInfo, args) {
+    const {wsUri, userDefinedTypes} = networkInfo.nodesByName[nodeName];
+    const api = await zombie.connect(wsUri, userDefinedTypes);
+
+    // parse arguments
+    const exitAfterSeconds = Number(args[0]);
+    const bridgedChain = require("./chains/" + args[1]);
+
+    // start listening to new blocks
+    let atLeastOneMessageReceived = false;
+    let atLeastOneMessageDelivered = false;
+    const unsubscribe = await api.rpc.chain.subscribeNewHeads(async function (header) {
+        const apiAtParent = await api.at(header.parentHash);
+        const apiAtCurrent = await api.at(header.hash);
+        const currentEvents = await apiAtCurrent.query.system.events();
+
+        const messagesReceived = currentEvents.find((record) => {
+            return record.event.section == bridgedChain.messagesPalletName
+                && record.event.method == "MessagesReceived";
+        }) != undefined;
+        const messagesDelivered = currentEvents.find((record) => {
+            return record.event.section == bridgedChain.messagesPalletName &&
+                record.event.method == "MessagesDelivered";
+        }) != undefined;
+        const hasMessageUpdates = messagesReceived || messagesDelivered;
+        atLeastOneMessageReceived = atLeastOneMessageReceived || messagesReceived;
+        atLeastOneMessageDelivered = atLeastOneMessageDelivered || messagesDelivered;
+
+        if (!hasMessageUpdates) {
+            // if there are no any message update transactions, we only expect mandatory GRANDPA
+            // headers and initial parachain headers
+            await utils.ensureOnlyMandatoryGrandpaHeadersImported(
+                bridgedChain,
+                apiAtParent,
+                apiAtCurrent,
+                currentEvents,
+            );
+            await utils.ensureOnlyInitialParachainHeaderImported(
+                bridgedChain,
+                apiAtParent,
+                apiAtCurrent,
+                currentEvents,
+            );
+        } else {
+            const messageTransactions = (messagesReceived ? 1 : 0) + (messagesDelivered ? 1 : 0);
+
+            // otherwise we only accept at most one GRANDPA header
+            const newGrandpaHeaders = utils.countGrandpaHeaderImports(bridgedChain, currentEvents);
+            if (newGrandpaHeaders > 1) {
+                utils.logEvents(currentEvents);
+                throw new Error("Unexpected relay chain header import: " + newGrandpaHeaders + " / " + messageTransactions);
+            }
+
+            // ...and at most one parachain header
+            const newParachainHeaders = utils.countParachainHeaderImports(bridgedChain, currentEvents);
+            if (newParachainHeaders > 1) {
+                utils.logEvents(currentEvents);
+                throw new Error("Unexpected parachain header import: " + newParachainHeaders + " / " + messageTransactions);
+            }
+        }
+    });
+
+    // wait until we have received + delivered messages OR until timeout
+    await utils.pollUntil(
+        exitAfterSeconds,
+        () => { return atLeastOneMessageReceived && atLeastOneMessageDelivered; },
+        () => { unsubscribe(); },
+        () => {
+            if (!atLeastOneMessageReceived) {
+                throw new Error("No messages received from bridged chain");
+            }
+            if (!atLeastOneMessageDelivered) {
+                throw new Error("No messages delivered to bridged chain");
+            }
+        },
+    );
+}
+
+module.exports = { run }
diff --git a/bridges/testing/framework/js-helpers/relayer-rewards.js b/bridges/testing/framework/js-helpers/relayer-rewards.js
new file mode 100644
index 00000000000..5347c649604
--- /dev/null
+++ b/bridges/testing/framework/js-helpers/relayer-rewards.js
@@ -0,0 +1,28 @@
+async function run(nodeName, networkInfo, args) {
+    const {wsUri, userDefinedTypes} = networkInfo.nodesByName[nodeName];
+    const api = await zombie.connect(wsUri, userDefinedTypes);
+
+    // TODO: could be replaced with https://github.com/polkadot-js/api/issues/4930 (depends on metadata v15) later
+    const relayerAccountAddress = args[0];
+    const laneId = args[1];
+    const bridgedChainId = args[2];
+    const relayerFundOwner = args[3];
+    const expectedRelayerReward = BigInt(args[4]);
+    while (true) {
+        const relayerReward = await api.query.bridgeRelayers.relayerRewards(
+            relayerAccountAddress,
+            { laneId: laneId, bridgedChainId: bridgedChainId, owner: relayerFundOwner }
+        );
+        if (relayerReward.isSome) {
+            const relayerRewardBalance = relayerReward.unwrap().toBigInt();
+            if (relayerRewardBalance > expectedRelayerReward) {
+                return relayerRewardBalance;
+            }
+        }
+
+        // else sleep and retry
+        await new Promise((resolve) => setTimeout(resolve, 6000));
+    }
+}
+
+module.exports = { run }
diff --git a/bridges/testing/framework/js-helpers/utils.js b/bridges/testing/framework/js-helpers/utils.js
new file mode 100644
index 00000000000..f6e9f5623b4
--- /dev/null
+++ b/bridges/testing/framework/js-helpers/utils.js
@@ -0,0 +1,103 @@
+module.exports = {
+    logEvents: function(events) {
+        let stringifiedEvents = "";
+        events.forEach((record) => {
+            if (stringifiedEvents != "") {
+                stringifiedEvents += ", ";
+            }
+            stringifiedEvents += record.event.section + "::" + record.event.method;
+        });
+        console.log("Block events: " + stringifiedEvents);
+    },
+    countGrandpaHeaderImports: function(bridgedChain, events) {
+        return events.reduce(
+            (count, record) => {
+                const { event } = record;
+                if (event.section == bridgedChain.grandpaPalletName && event.method == "UpdatedBestFinalizedHeader") {
+                    count += 1;
+                }
+                return count;
+            },
+            0,
+        );
+    },
+    countParachainHeaderImports: function(bridgedChain, events) {
+        return events.reduce(
+            (count, record) => {
+                const { event } = record;
+                if (event.section == bridgedChain.parachainsPalletName && event.method == "UpdatedParachainHead") {
+                    count += 1;
+                }
+                return count;
+            },
+            0,
+        );
+    },
+    pollUntil: async function(
+        timeoutInSecs,
+        predicate,
+        cleanup,
+        onFailure,
+    )  {
+        const begin = new Date().getTime();
+        const end = begin + timeoutInSecs * 1000;
+        while (new Date().getTime() < end) {
+            if (predicate()) {
+                cleanup();
+                return;
+            }
+            await new Promise(resolve => setTimeout(resolve, 100));
+        }
+
+        cleanup();
+        onFailure();
+    },
+    ensureOnlyMandatoryGrandpaHeadersImported: async function(
+        bridgedChain,
+        apiAtParent,
+        apiAtCurrent,
+        currentEvents,
+    ) {
+        // remember id of bridged relay chain GRANDPA authorities set at parent block
+        const authoritySetAtParent = await apiAtParent.query[bridgedChain.grandpaPalletName].currentAuthoritySet();
+        const authoritySetIdAtParent = authoritySetAtParent["setId"];
+
+        // now read the id of bridged relay chain GRANDPA authorities set at current block
+        const authoritySetAtCurrent = await apiAtCurrent.query[bridgedChain.grandpaPalletName].currentAuthoritySet();
+        const authoritySetIdAtCurrent = authoritySetAtCurrent["setId"];
+
+        // we expect to see no more than `authoritySetIdAtCurrent - authoritySetIdAtParent` new GRANDPA headers
+        const maxNewGrandpaHeaders = authoritySetIdAtCurrent - authoritySetIdAtParent;
+        const newGrandpaHeaders = module.exports.countGrandpaHeaderImports(bridgedChain, currentEvents);
+
+        // check that our assumptions are correct
+        if (newGrandpaHeaders > maxNewGrandpaHeaders) {
+            module.exports.logEvents(currentEvents);
+            throw new Error("Unexpected relay chain header import: " + newGrandpaHeaders + " / " + maxNewGrandpaHeaders);
+        }
+
+        return newGrandpaHeaders;
+    },
+    ensureOnlyInitialParachainHeaderImported: async function(
+        bridgedChain,
+        apiAtParent,
+        apiAtCurrent,
+        currentEvents,
+    ) {
+        // remember whether we already know bridged parachain header at a parent block
+        const bestBridgedParachainHeader = await apiAtParent.query[bridgedChain.parachainsPalletName].parasInfo(bridgedChain.bridgedBridgeHubParaId);;
+        const hasBestBridgedParachainHeader = bestBridgedParachainHeader.isSome;
+
+        // we expect to see: no more than `1` bridged parachain header if there were no parachain header before.
+        const maxNewParachainHeaders = hasBestBridgedParachainHeader ? 0 : 1;
+        const newParachainHeaders = module.exports.countParachainHeaderImports(bridgedChain, currentEvents);
+
+        // check that our assumptions are correct
+        if (newParachainHeaders > maxNewParachainHeaders) {
+            module.exports.logEvents(currentEvents);
+            throw new Error("Unexpected parachain header import: " + newParachainHeaders + " / " + maxNewParachainHeaders);
+        }
+
+        return hasBestBridgedParachainHeader;
+    },
+}
diff --git a/bridges/testing/framework/js-helpers/wait-hrmp-channel-opened.js b/bridges/testing/framework/js-helpers/wait-hrmp-channel-opened.js
new file mode 100644
index 00000000000..765d48cc498
--- /dev/null
+++ b/bridges/testing/framework/js-helpers/wait-hrmp-channel-opened.js
@@ -0,0 +1,22 @@
+async function run(nodeName, networkInfo, args) {
+    const {wsUri, userDefinedTypes} = networkInfo.nodesByName[nodeName];
+    const api = await zombie.connect(wsUri, userDefinedTypes);
+
+    const sibling = args[0];
+
+    while (true) {
+        const messagingStateAsObj = await api.query.parachainSystem.relevantMessagingState();
+        const messagingState = api.createType("Option<CumulusPalletParachainSystemRelayStateSnapshotMessagingStateSnapshot>", messagingStateAsObj);
+        if (messagingState.isSome) {
+            const egressChannels = messagingState.unwrap().egressChannels;
+            if (egressChannels.find(x => x[0] == sibling)) {
+                return;
+            }
+        }
+
+        // else sleep and retry
+        await new Promise((resolve) => setTimeout(resolve, 6000));
+    }
+}
+
+module.exports = { run }
diff --git a/bridges/testing/framework/js-helpers/wrapped-assets-balance.js b/bridges/testing/framework/js-helpers/wrapped-assets-balance.js
new file mode 100644
index 00000000000..27287118547
--- /dev/null
+++ b/bridges/testing/framework/js-helpers/wrapped-assets-balance.js
@@ -0,0 +1,26 @@
+async function run(nodeName, networkInfo, args) {
+    const {wsUri, userDefinedTypes} = networkInfo.nodesByName[nodeName];
+    const api = await zombie.connect(wsUri, userDefinedTypes);
+
+    // TODO: could be replaced with https://github.com/polkadot-js/api/issues/4930 (depends on metadata v15) later
+    const accountAddress = args[0];
+    const expectedForeignAssetBalance = BigInt(args[1]);
+    const bridgedNetworkName = args[2];
+    while (true) {
+        const foreignAssetAccount = await api.query.foreignAssets.account(
+            { parents: 2, interior: { X1: { GlobalConsensus: bridgedNetworkName } } },
+            accountAddress
+        );
+        if (foreignAssetAccount.isSome) {
+            const foreignAssetAccountBalance = foreignAssetAccount.unwrap().balance.toBigInt();
+            if (foreignAssetAccountBalance > expectedForeignAssetBalance) {
+                return foreignAssetAccountBalance;
+            }
+        }
+
+        // else sleep and retry
+        await new Promise((resolve) => setTimeout(resolve, 6000));
+    }
+}
+
+module.exports = { run }
diff --git a/bridges/testing/framework/utils/bridges.sh b/bridges/testing/framework/utils/bridges.sh
new file mode 100755
index 00000000000..7c839946158
--- /dev/null
+++ b/bridges/testing/framework/utils/bridges.sh
@@ -0,0 +1,309 @@
+#!/bin/bash
+
+function relayer_path() {
+    local default_path=~/local_bridge_testing/bin/substrate-relay
+    local path="${SUBSTRATE_RELAY_BINARY:-$default_path}"
+    echo "$path"
+}
+
+function ensure_relayer() {
+    local path=$(relayer_path)
+    if [[ ! -f "$path" ]]; then
+        echo "  Required substrate-relay binary '$path' does not exist!"
+        echo "  You need to build it and copy to this location!"
+        echo "  Please, check ./parachains/runtimes/bridge-hubs/README.md (Prepare/Build/Deploy)"
+        exit 1
+    fi
+
+    echo $path
+}
+
+function ensure_polkadot_js_api() {
+    if ! which polkadot-js-api &> /dev/null; then
+        echo ''
+        echo 'Required command `polkadot-js-api` not in PATH, please, install, e.g.:'
+        echo "npm install -g @polkadot/api-cli@beta"
+        echo "      or"
+        echo "yarn global add @polkadot/api-cli"
+        echo ''
+        exit 1
+    fi
+    if ! which jq &> /dev/null; then
+        echo ''
+        echo 'Required command `jq` not in PATH, please, install, e.g.:'
+        echo "apt install -y jq"
+        echo ''
+        exit 1
+    fi
+    generate_hex_encoded_call_data "check" "--"
+    local retVal=$?
+    if [ $retVal -ne 0 ]; then
+        echo ""
+        echo ""
+        echo "-------------------"
+        echo "Installing (nodejs) sub module: ${BASH_SOURCE%/*}/generate_hex_encoded_call"
+        pushd ${BASH_SOURCE%/*}/generate_hex_encoded_call
+        npm install
+        popd
+    fi
+}
+
+function call_polkadot_js_api() {
+    # --noWait: without that argument `polkadot-js-api` waits until transaction is included into the block.
+    #           With it, it just submits it to the tx pool and exits.
+    # --nonce -1: means to compute transaction nonce using `system_accountNextIndex` RPC, which includes all
+    #             transaction that are in the tx pool.
+    polkadot-js-api --noWait --nonce -1 "$@"
+}
+
+function generate_hex_encoded_call_data() {
+    local type=$1
+    local endpoint=$2
+    local output=$3
+    shift
+    shift
+    shift
+    echo "Input params: $@"
+
+    node ${BASH_SOURCE%/*}/../utils/generate_hex_encoded_call "$type" "$endpoint" "$output" "$@"
+    local retVal=$?
+
+    if [ $type != "check" ]; then
+        local hex_encoded_data=$(cat $output)
+        echo "Generated hex-encoded bytes to file '$output': $hex_encoded_data"
+    fi
+
+    return $retVal
+}
+
+function transfer_balance() {
+    local runtime_para_endpoint=$1
+    local seed=$2
+    local target_account=$3
+    local amount=$4
+    echo "  calling transfer_balance:"
+    echo "      runtime_para_endpoint: ${runtime_para_endpoint}"
+    echo "      seed: ${seed}"
+    echo "      target_account: ${target_account}"
+    echo "      amount: ${amount}"
+    echo "--------------------------------------------------"
+
+    call_polkadot_js_api \
+        --ws "${runtime_para_endpoint}" \
+        --seed "${seed?}" \
+        tx.balances.transferAllowDeath \
+            "${target_account}" \
+            "${amount}"
+}
+
+function send_governance_transact() {
+    local relay_url=$1
+    local relay_chain_seed=$2
+    local para_id=$3
+    local hex_encoded_data=$4
+    local require_weight_at_most_ref_time=$5
+    local require_weight_at_most_proof_size=$6
+    echo "  calling send_governance_transact:"
+    echo "      relay_url: ${relay_url}"
+    echo "      relay_chain_seed: ${relay_chain_seed}"
+    echo "      para_id: ${para_id}"
+    echo "      hex_encoded_data: ${hex_encoded_data}"
+    echo "      require_weight_at_most_ref_time: ${require_weight_at_most_ref_time}"
+    echo "      require_weight_at_most_proof_size: ${require_weight_at_most_proof_size}"
+    echo "      params:"
+
+    local dest=$(jq --null-input \
+                    --arg para_id "$para_id" \
+                    '{ "V3": { "parents": 0, "interior": { "X1": { "Parachain": $para_id } } } }')
+
+    local message=$(jq --null-input \
+                       --argjson hex_encoded_data $hex_encoded_data \
+                       --arg require_weight_at_most_ref_time "$require_weight_at_most_ref_time" \
+                       --arg require_weight_at_most_proof_size "$require_weight_at_most_proof_size" \
+                       '
+                       {
+                          "V3": [
+                                  {
+                                    "UnpaidExecution": {
+                                        "weight_limit": "Unlimited"
+                                    }
+                                  },
+                                  {
+                                    "Transact": {
+                                      "origin_kind": "Superuser",
+                                      "require_weight_at_most": {
+                                        "ref_time": $require_weight_at_most_ref_time,
+                                        "proof_size": $require_weight_at_most_proof_size,
+                                      },
+                                      "call": {
+                                        "encoded": $hex_encoded_data
+                                      }
+                                    }
+                                  }
+                          ]
+                        }
+                        ')
+
+    echo ""
+    echo "          dest:"
+    echo "${dest}"
+    echo ""
+    echo "          message:"
+    echo "${message}"
+    echo ""
+    echo "--------------------------------------------------"
+
+    call_polkadot_js_api \
+        --ws "${relay_url?}" \
+        --seed "${relay_chain_seed?}" \
+        --sudo \
+        tx.xcmPallet.send \
+            "${dest}" \
+            "${message}"
+}
+
+function open_hrmp_channels() {
+    local relay_url=$1
+    local relay_chain_seed=$2
+    local sender_para_id=$3
+    local recipient_para_id=$4
+    local max_capacity=$5
+    local max_message_size=$6
+    echo "  calling open_hrmp_channels:"
+    echo "      relay_url: ${relay_url}"
+    echo "      relay_chain_seed: ${relay_chain_seed}"
+    echo "      sender_para_id: ${sender_para_id}"
+    echo "      recipient_para_id: ${recipient_para_id}"
+    echo "      max_capacity: ${max_capacity}"
+    echo "      max_message_size: ${max_message_size}"
+    echo "      params:"
+    echo "--------------------------------------------------"
+    call_polkadot_js_api \
+        --ws "${relay_url?}" \
+        --seed "${relay_chain_seed?}" \
+        --sudo \
+        tx.hrmp.forceOpenHrmpChannel \
+            ${sender_para_id} \
+            ${recipient_para_id} \
+            ${max_capacity} \
+            ${max_message_size}
+}
+
+function force_xcm_version() {
+    local relay_url=$1
+    local relay_chain_seed=$2
+    local runtime_para_id=$3
+    local runtime_para_endpoint=$4
+    local dest=$5
+    local xcm_version=$6
+    echo "  calling force_xcm_version:"
+    echo "      relay_url: ${relay_url}"
+    echo "      relay_chain_seed: ${relay_chain_seed}"
+    echo "      runtime_para_id: ${runtime_para_id}"
+    echo "      runtime_para_endpoint: ${runtime_para_endpoint}"
+    echo "      dest: ${dest}"
+    echo "      xcm_version: ${xcm_version}"
+    echo "      params:"
+
+    # 1. generate data for Transact (PolkadotXcm::force_xcm_version)
+    local tmp_output_file=$(mktemp)
+    generate_hex_encoded_call_data "force-xcm-version" "${runtime_para_endpoint}" "${tmp_output_file}" "$dest" "$xcm_version"
+    local hex_encoded_data=$(cat $tmp_output_file)
+
+    # 2. trigger governance call
+    send_governance_transact "${relay_url}" "${relay_chain_seed}" "${runtime_para_id}" "${hex_encoded_data}" 200000000 12000
+}
+
+function force_create_foreign_asset() {
+    local relay_url=$1
+    local relay_chain_seed=$2
+    local runtime_para_id=$3
+    local runtime_para_endpoint=$4
+    local asset_multilocation=$5
+    local asset_owner_account_id=$6
+    local min_balance=$7
+    local is_sufficient=$8
+    echo "  calling force_create_foreign_asset:"
+    echo "      relay_url: ${relay_url}"
+    echo "      relay_chain_seed: ${relay_chain_seed}"
+    echo "      runtime_para_id: ${runtime_para_id}"
+    echo "      runtime_para_endpoint: ${runtime_para_endpoint}"
+    echo "      asset_multilocation: ${asset_multilocation}"
+    echo "      asset_owner_account_id: ${asset_owner_account_id}"
+    echo "      min_balance: ${min_balance}"
+    echo "      is_sufficient: ${is_sufficient}"
+    echo "      params:"
+
+    # 1. generate data for Transact (ForeignAssets::force_create)
+    local tmp_output_file=$(mktemp)
+    generate_hex_encoded_call_data "force-create-asset" "${runtime_para_endpoint}" "${tmp_output_file}" "$asset_multilocation" "$asset_owner_account_id" $is_sufficient $min_balance
+    local hex_encoded_data=$(cat $tmp_output_file)
+
+    # 2. trigger governance call
+    send_governance_transact "${relay_url}" "${relay_chain_seed}" "${runtime_para_id}" "${hex_encoded_data}" 200000000 12000
+}
+
+function limited_reserve_transfer_assets() {
+    local url=$1
+    local seed=$2
+    local destination=$3
+    local beneficiary=$4
+    local assets=$5
+    local fee_asset_item=$6
+    local weight_limit=$7
+    echo "  calling limited_reserve_transfer_assets:"
+    echo "      url: ${url}"
+    echo "      seed: ${seed}"
+    echo "      destination: ${destination}"
+    echo "      beneficiary: ${beneficiary}"
+    echo "      assets: ${assets}"
+    echo "      fee_asset_item: ${fee_asset_item}"
+    echo "      weight_limit: ${weight_limit}"
+    echo ""
+    echo "--------------------------------------------------"
+
+    call_polkadot_js_api \
+        --ws "${url?}" \
+        --seed "${seed?}" \
+        tx.polkadotXcm.limitedReserveTransferAssets \
+            "${destination}" \
+            "${beneficiary}" \
+            "${assets}" \
+            "${fee_asset_item}" \
+            "${weight_limit}"
+}
+
+function claim_rewards() {
+    local runtime_para_endpoint=$1
+    local seed=$2
+    local lane_id=$3
+    local bridged_chain_id=$4
+    local owner=$5
+    echo "  calling claim_rewards:"
+    echo "      runtime_para_endpoint: ${runtime_para_endpoint}"
+    echo "      seed: ${seed}"
+    echo "      lane_id: ${lane_id}"
+    echo "      bridged_chain_id: ${bridged_chain_id}"
+    echo "      owner: ${owner}"
+    echo ""
+
+    local rewards_account_params=$(jq --null-input \
+                                      --arg lane_id "$lane_id" \
+                                      --arg bridged_chain_id "$bridged_chain_id" \
+                                      --arg owner "$owner" \
+                    '{
+                        "laneId": $lane_id,
+                        "bridgedChainId": $bridged_chain_id,
+                        "owner": $owner
+                     }')
+
+    echo "          rewards_account_params:"
+    echo "${rewards_account_params}"
+    echo "--------------------------------------------------"
+
+    call_polkadot_js_api \
+        --ws "${runtime_para_endpoint}" \
+        --seed "${seed?}" \
+        tx.bridgeRelayers.claimRewards \
+            "${rewards_account_params}"
+}
\ No newline at end of file
diff --git a/bridges/testing/framework/utils/common.sh b/bridges/testing/framework/utils/common.sh
new file mode 100644
index 00000000000..06f41320be1
--- /dev/null
+++ b/bridges/testing/framework/utils/common.sh
@@ -0,0 +1,45 @@
+#!/bin/bash
+
+function start_background_process() {
+    local command=$1
+    local log_file=$2
+    local __pid=$3
+
+    $command > $log_file 2>&1 &
+    eval $__pid="'$!'"
+}
+
+function wait_for_process_file() {
+    local pid=$1
+    local file=$2
+    local timeout=$3
+    local __found=$4
+
+    local time=0
+    until [ -e $file ]; do
+      if ! kill -0 $pid; then
+        echo "Process finished unsuccessfully"
+        return
+      fi
+      if (( time++ >= timeout )); then
+        echo "Timeout waiting for file $file: $timeout seconds"
+        eval $__found=0
+        return
+      fi
+      sleep 1
+    done
+
+    echo "File $file found after $time seconds"
+    eval $__found=1
+}
+
+function ensure_process_file() {
+    local pid=$1
+    local file=$2
+    local timeout=$3
+
+    wait_for_process_file $pid $file $timeout file_found
+    if [ "$file_found" != "1" ]; then
+      exit 1
+    fi
+}
diff --git a/bridges/testing/framework/utils/generate_hex_encoded_call/index.js b/bridges/testing/framework/utils/generate_hex_encoded_call/index.js
new file mode 100644
index 00000000000..30f89d754ce
--- /dev/null
+++ b/bridges/testing/framework/utils/generate_hex_encoded_call/index.js
@@ -0,0 +1,165 @@
+const fs = require("fs");
+const { exit } = require("process");
+const { WsProvider, ApiPromise } = require("@polkadot/api");
+const util = require("@polkadot/util");
+
+// connect to a substrate chain and return the api object
+async function connect(endpoint, types = {}) {
+	const provider = new WsProvider(endpoint);
+	const api = await ApiPromise.create({
+		provider,
+		types,
+		throwOnConnect: false,
+	});
+	return api;
+}
+
+function writeHexEncodedBytesToOutput(method, outputFile) {
+	console.log("Payload (hex): ", method.toHex());
+	console.log("Payload (bytes): ", Array.from(method.toU8a()));
+	console.log("Payload (plain): ", JSON.stringify(method));
+	fs.writeFileSync(outputFile, JSON.stringify(Array.from(method.toU8a())));
+}
+
+function remarkWithEvent(endpoint, outputFile) {
+	console.log(`Generating remarkWithEvent from RPC endpoint: ${endpoint} to outputFile: ${outputFile}`);
+	connect(endpoint)
+		.then((api) => {
+			const call = api.tx.system.remarkWithEvent("Hello");
+			writeHexEncodedBytesToOutput(call.method, outputFile);
+			exit(0);
+		})
+		.catch((e) => {
+			console.error(e);
+			exit(1);
+		});
+}
+
+function addExporterConfig(endpoint, outputFile, bridgedNetwork, bridgeConfig) {
+	console.log(`Generating addExporterConfig from RPC endpoint: ${endpoint} to outputFile: ${outputFile} based on bridgedNetwork: ${bridgedNetwork}, bridgeConfig: ${bridgeConfig}`);
+	connect(endpoint)
+		.then((api) => {
+			const call = api.tx.bridgeTransfer.addExporterConfig(bridgedNetwork, JSON.parse(bridgeConfig));
+			writeHexEncodedBytesToOutput(call.method, outputFile);
+			exit(0);
+		})
+		.catch((e) => {
+			console.error(e);
+			exit(1);
+		});
+}
+
+function addUniversalAlias(endpoint, outputFile, location, junction) {
+	console.log(`Generating addUniversalAlias from RPC endpoint: ${endpoint} to outputFile: ${outputFile} based on location: ${location}, junction: ${junction}`);
+	connect(endpoint)
+		.then((api) => {
+			const call = api.tx.bridgeTransfer.addUniversalAlias(JSON.parse(location), JSON.parse(junction));
+			writeHexEncodedBytesToOutput(call.method, outputFile);
+			exit(0);
+		})
+		.catch((e) => {
+			console.error(e);
+			exit(1);
+		});
+}
+
+function addReserveLocation(endpoint, outputFile, reserve_location) {
+	console.log(`Generating addReserveLocation from RPC endpoint: ${endpoint} to outputFile: ${outputFile} based on reserve_location: ${reserve_location}`);
+	connect(endpoint)
+		.then((api) => {
+			const call = api.tx.bridgeTransfer.addReserveLocation(JSON.parse(reserve_location));
+			writeHexEncodedBytesToOutput(call.method, outputFile);
+			exit(0);
+		})
+		.catch((e) => {
+			console.error(e);
+			exit(1);
+		});
+}
+
+function removeExporterConfig(endpoint, outputFile, bridgedNetwork) {
+	console.log(`Generating removeExporterConfig from RPC endpoint: ${endpoint} to outputFile: ${outputFile} based on bridgedNetwork: ${bridgedNetwork}`);
+	connect(endpoint)
+		.then((api) => {
+			const call = api.tx.bridgeTransfer.removeExporterConfig(bridgedNetwork);
+			writeHexEncodedBytesToOutput(call.method, outputFile);
+			exit(0);
+		})
+		.catch((e) => {
+			console.error(e);
+			exit(1);
+		});
+}
+
+function forceCreateAsset(endpoint, outputFile, assetId, assetOwnerAccountId, isSufficient, minBalance) {
+	var isSufficient = isSufficient == "true" ? true : false;
+	console.log(`Generating forceCreateAsset from RPC endpoint: ${endpoint} to outputFile: ${outputFile} based on assetId: ${assetId}, assetOwnerAccountId: ${assetOwnerAccountId}, isSufficient: ${isSufficient}, minBalance: ${minBalance}`);
+	connect(endpoint)
+		.then((api) => {
+			const call = api.tx.foreignAssets.forceCreate(JSON.parse(assetId), assetOwnerAccountId, isSufficient, minBalance);
+			writeHexEncodedBytesToOutput(call.method, outputFile);
+			exit(0);
+		})
+		.catch((e) => {
+			console.error(e);
+			exit(1);
+		});
+}
+
+function forceXcmVersion(endpoint, outputFile, dest, xcm_version) {
+	console.log(`Generating forceXcmVersion from RPC endpoint: ${endpoint} to outputFile: ${outputFile}, dest: ${dest}, xcm_version: ${xcm_version}`);
+	connect(endpoint)
+		.then((api) => {
+			const call = api.tx.polkadotXcm.forceXcmVersion(JSON.parse(dest), xcm_version);
+			writeHexEncodedBytesToOutput(call.method, outputFile);
+			exit(0);
+		})
+		.catch((e) => {
+			console.error(e);
+			exit(1);
+		});
+}
+
+if (!process.argv[2] || !process.argv[3]) {
+	console.log("usage: node ./script/generate_hex_encoded_call <type> <endpoint> <output hex-encoded data file> <input message>");
+	exit(1);
+}
+
+const type = process.argv[2];
+const rpcEnpoint = process.argv[3];
+const output = process.argv[4];
+const inputArgs = process.argv.slice(5, process.argv.length);
+console.log(`Generating hex-encoded call data for:`);
+console.log(`	type: ${type}`);
+console.log(`	rpcEnpoint: ${rpcEnpoint}`);
+console.log(`	output: ${output}`);
+console.log(`	inputArgs: ${inputArgs}`);
+
+switch (type) {
+	case 'remark-with-event':
+		remarkWithEvent(rpcEnpoint, output);
+		break;
+	case 'add-exporter-config':
+		addExporterConfig(rpcEnpoint, output, inputArgs[0], inputArgs[1]);
+		break;
+	case 'remove-exporter-config':
+		removeExporterConfig(rpcEnpoint, output, inputArgs[0], inputArgs[1]);
+		break;
+	case 'add-universal-alias':
+		addUniversalAlias(rpcEnpoint, output, inputArgs[0], inputArgs[1]);
+		break;
+	case 'add-reserve-location':
+		addReserveLocation(rpcEnpoint, output, inputArgs[0]);
+		break;
+	case 'force-create-asset':
+		forceCreateAsset(rpcEnpoint, output, inputArgs[0], inputArgs[1], inputArgs[2], inputArgs[3]);
+		break;
+	case 'force-xcm-version':
+		forceXcmVersion(rpcEnpoint, output, inputArgs[0], inputArgs[1]);
+		break;
+	case 'check':
+		console.log(`Checking nodejs installation, if you see this everything is ready!`);
+		break;
+	default:
+		console.log(`Sorry, we are out of ${type} - not yet supported!`);
+}
diff --git a/bridges/testing/framework/utils/generate_hex_encoded_call/package-lock.json b/bridges/testing/framework/utils/generate_hex_encoded_call/package-lock.json
new file mode 100644
index 00000000000..b2dddaa19ed
--- /dev/null
+++ b/bridges/testing/framework/utils/generate_hex_encoded_call/package-lock.json
@@ -0,0 +1,759 @@
+{
+  "name": "y",
+  "version": "y",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "y",
+      "version": "y",
+      "license": "MIT",
+      "dependencies": {
+        "@polkadot/api": "^10.11",
+        "@polkadot/util": "^12.6"
+      }
+    },
+    "node_modules/@noble/curves": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.3.0.tgz",
+      "integrity": "sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA==",
+      "dependencies": {
+        "@noble/hashes": "1.3.3"
+      },
+      "funding": {
+        "url": "https://paulmillr.com/funding/"
+      }
+    },
+    "node_modules/@noble/hashes": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz",
+      "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==",
+      "engines": {
+        "node": ">= 16"
+      },
+      "funding": {
+        "url": "https://paulmillr.com/funding/"
+      }
+    },
+    "node_modules/@polkadot/api": {
+      "version": "10.11.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/api/-/api-10.11.2.tgz",
+      "integrity": "sha512-AorCZxCWCoTtdbl4DPUZh+ACe/pbLIS1BkdQY0AFJuZllm0x/yWzjgampcPd5jQAA/O3iKShRBkZqj6Mk9yG/A==",
+      "dependencies": {
+        "@polkadot/api-augment": "10.11.2",
+        "@polkadot/api-base": "10.11.2",
+        "@polkadot/api-derive": "10.11.2",
+        "@polkadot/keyring": "^12.6.2",
+        "@polkadot/rpc-augment": "10.11.2",
+        "@polkadot/rpc-core": "10.11.2",
+        "@polkadot/rpc-provider": "10.11.2",
+        "@polkadot/types": "10.11.2",
+        "@polkadot/types-augment": "10.11.2",
+        "@polkadot/types-codec": "10.11.2",
+        "@polkadot/types-create": "10.11.2",
+        "@polkadot/types-known": "10.11.2",
+        "@polkadot/util": "^12.6.2",
+        "@polkadot/util-crypto": "^12.6.2",
+        "eventemitter3": "^5.0.1",
+        "rxjs": "^7.8.1",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@polkadot/api-augment": {
+      "version": "10.11.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/api-augment/-/api-augment-10.11.2.tgz",
+      "integrity": "sha512-PTpnqpezc75qBqUtgrc0GYB8h9UHjfbHSRZamAbecIVAJ2/zc6CqtnldeaBlIu1IKTgBzi3FFtTyYu+ZGbNT2Q==",
+      "dependencies": {
+        "@polkadot/api-base": "10.11.2",
+        "@polkadot/rpc-augment": "10.11.2",
+        "@polkadot/types": "10.11.2",
+        "@polkadot/types-augment": "10.11.2",
+        "@polkadot/types-codec": "10.11.2",
+        "@polkadot/util": "^12.6.2",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@polkadot/api-base": {
+      "version": "10.11.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/api-base/-/api-base-10.11.2.tgz",
+      "integrity": "sha512-4LIjaUfO9nOzilxo7XqzYKCNMtmUypdk8oHPdrRnSjKEsnK7vDsNi+979z2KXNXd2KFSCFHENmI523fYnMnReg==",
+      "dependencies": {
+        "@polkadot/rpc-core": "10.11.2",
+        "@polkadot/types": "10.11.2",
+        "@polkadot/util": "^12.6.2",
+        "rxjs": "^7.8.1",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@polkadot/api-derive": {
+      "version": "10.11.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/api-derive/-/api-derive-10.11.2.tgz",
+      "integrity": "sha512-m3BQbPionkd1iSlknddxnL2hDtolPIsT+aRyrtn4zgMRPoLjHFmTmovvg8RaUyYofJtZeYrnjMw0mdxiSXx7eA==",
+      "dependencies": {
+        "@polkadot/api": "10.11.2",
+        "@polkadot/api-augment": "10.11.2",
+        "@polkadot/api-base": "10.11.2",
+        "@polkadot/rpc-core": "10.11.2",
+        "@polkadot/types": "10.11.2",
+        "@polkadot/types-codec": "10.11.2",
+        "@polkadot/util": "^12.6.2",
+        "@polkadot/util-crypto": "^12.6.2",
+        "rxjs": "^7.8.1",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@polkadot/keyring": {
+      "version": "12.6.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/keyring/-/keyring-12.6.2.tgz",
+      "integrity": "sha512-O3Q7GVmRYm8q7HuB3S0+Yf/q/EB2egKRRU3fv9b3B7V+A52tKzA+vIwEmNVaD1g5FKW9oB97rmpggs0zaKFqHw==",
+      "dependencies": {
+        "@polkadot/util": "12.6.2",
+        "@polkadot/util-crypto": "12.6.2",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "peerDependencies": {
+        "@polkadot/util": "12.6.2",
+        "@polkadot/util-crypto": "12.6.2"
+      }
+    },
+    "node_modules/@polkadot/networks": {
+      "version": "12.6.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/networks/-/networks-12.6.2.tgz",
+      "integrity": "sha512-1oWtZm1IvPWqvMrldVH6NI2gBoCndl5GEwx7lAuQWGr7eNL+6Bdc5K3Z9T0MzFvDGoi2/CBqjX9dRKo39pDC/w==",
+      "dependencies": {
+        "@polkadot/util": "12.6.2",
+        "@substrate/ss58-registry": "^1.44.0",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@polkadot/rpc-augment": {
+      "version": "10.11.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/rpc-augment/-/rpc-augment-10.11.2.tgz",
+      "integrity": "sha512-9AhT0WW81/8jYbRcAC6PRmuxXqNhJje8OYiulBQHbG1DTCcjAfz+6VQBke9BwTStzPq7d526+yyBKD17O3zlAA==",
+      "dependencies": {
+        "@polkadot/rpc-core": "10.11.2",
+        "@polkadot/types": "10.11.2",
+        "@polkadot/types-codec": "10.11.2",
+        "@polkadot/util": "^12.6.2",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@polkadot/rpc-core": {
+      "version": "10.11.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/rpc-core/-/rpc-core-10.11.2.tgz",
+      "integrity": "sha512-Ot0CFLWx8sZhLZog20WDuniPA01Bk2StNDsdAQgcFKPwZw6ShPaZQCHuKLQK6I6DodOrem9FXX7c1hvoKJP5Ww==",
+      "dependencies": {
+        "@polkadot/rpc-augment": "10.11.2",
+        "@polkadot/rpc-provider": "10.11.2",
+        "@polkadot/types": "10.11.2",
+        "@polkadot/util": "^12.6.2",
+        "rxjs": "^7.8.1",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@polkadot/rpc-provider": {
+      "version": "10.11.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/rpc-provider/-/rpc-provider-10.11.2.tgz",
+      "integrity": "sha512-he5jWMpDJp7e+vUzTZDzpkB7ps3H8psRally+/ZvZZScPvFEjfczT7I1WWY9h58s8+ImeVP/lkXjL9h/gUOt3Q==",
+      "dependencies": {
+        "@polkadot/keyring": "^12.6.2",
+        "@polkadot/types": "10.11.2",
+        "@polkadot/types-support": "10.11.2",
+        "@polkadot/util": "^12.6.2",
+        "@polkadot/util-crypto": "^12.6.2",
+        "@polkadot/x-fetch": "^12.6.2",
+        "@polkadot/x-global": "^12.6.2",
+        "@polkadot/x-ws": "^12.6.2",
+        "eventemitter3": "^5.0.1",
+        "mock-socket": "^9.3.1",
+        "nock": "^13.4.0",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "optionalDependencies": {
+        "@substrate/connect": "0.7.35"
+      }
+    },
+    "node_modules/@polkadot/types": {
+      "version": "10.11.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/types/-/types-10.11.2.tgz",
+      "integrity": "sha512-d52j3xXni+C8GdYZVTSfu8ROAnzXFMlyRvXtor0PudUc8UQHOaC4+mYAkTBGA2gKdmL8MHSfRSbhcxHhsikY6Q==",
+      "dependencies": {
+        "@polkadot/keyring": "^12.6.2",
+        "@polkadot/types-augment": "10.11.2",
+        "@polkadot/types-codec": "10.11.2",
+        "@polkadot/types-create": "10.11.2",
+        "@polkadot/util": "^12.6.2",
+        "@polkadot/util-crypto": "^12.6.2",
+        "rxjs": "^7.8.1",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@polkadot/types-augment": {
+      "version": "10.11.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/types-augment/-/types-augment-10.11.2.tgz",
+      "integrity": "sha512-8eB8ew04wZiE5GnmFvEFW1euJWmF62SGxb1O+8wL3zoUtB9Xgo1vB6w6xbTrd+HLV6jNSeXXnbbF1BEUvi9cNg==",
+      "dependencies": {
+        "@polkadot/types": "10.11.2",
+        "@polkadot/types-codec": "10.11.2",
+        "@polkadot/util": "^12.6.2",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@polkadot/types-codec": {
+      "version": "10.11.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/types-codec/-/types-codec-10.11.2.tgz",
+      "integrity": "sha512-3xjOQL+LOOMzYqlgP9ROL0FQnzU8lGflgYewzau7AsDlFziSEtb49a9BpYo6zil4koC+QB8zQ9OHGFumG08T8w==",
+      "dependencies": {
+        "@polkadot/util": "^12.6.2",
+        "@polkadot/x-bigint": "^12.6.2",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@polkadot/types-create": {
+      "version": "10.11.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/types-create/-/types-create-10.11.2.tgz",
+      "integrity": "sha512-SJt23NxYvefRxVZZm6mT9ed1pR6FDoIGQ3xUpbjhTLfU2wuhpKjekMVorYQ6z/gK2JLMu2kV92Ardsz+6GX5XQ==",
+      "dependencies": {
+        "@polkadot/types-codec": "10.11.2",
+        "@polkadot/util": "^12.6.2",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@polkadot/types-known": {
+      "version": "10.11.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/types-known/-/types-known-10.11.2.tgz",
+      "integrity": "sha512-kbEIX7NUQFxpDB0FFGNyXX/odY7jbp56RGD+Z4A731fW2xh/DgAQrI994xTzuh0c0EqPE26oQm3kATSpseqo9w==",
+      "dependencies": {
+        "@polkadot/networks": "^12.6.2",
+        "@polkadot/types": "10.11.2",
+        "@polkadot/types-codec": "10.11.2",
+        "@polkadot/types-create": "10.11.2",
+        "@polkadot/util": "^12.6.2",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@polkadot/types-support": {
+      "version": "10.11.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/types-support/-/types-support-10.11.2.tgz",
+      "integrity": "sha512-X11hoykFYv/3efg4coZy2hUOUc97JhjQMJLzDhHniFwGLlYU8MeLnPdCVGkXx0xDDjTo4/ptS1XpZ5HYcg+gRw==",
+      "dependencies": {
+        "@polkadot/util": "^12.6.2",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@polkadot/util": {
+      "version": "12.6.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/util/-/util-12.6.2.tgz",
+      "integrity": "sha512-l8TubR7CLEY47240uki0TQzFvtnxFIO7uI/0GoWzpYD/O62EIAMRsuY01N4DuwgKq2ZWD59WhzsLYmA5K6ksdw==",
+      "dependencies": {
+        "@polkadot/x-bigint": "12.6.2",
+        "@polkadot/x-global": "12.6.2",
+        "@polkadot/x-textdecoder": "12.6.2",
+        "@polkadot/x-textencoder": "12.6.2",
+        "@types/bn.js": "^5.1.5",
+        "bn.js": "^5.2.1",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@polkadot/util-crypto": {
+      "version": "12.6.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/util-crypto/-/util-crypto-12.6.2.tgz",
+      "integrity": "sha512-FEWI/dJ7wDMNN1WOzZAjQoIcCP/3vz3wvAp5QQm+lOrzOLj0iDmaIGIcBkz8HVm3ErfSe/uKP0KS4jgV/ib+Mg==",
+      "dependencies": {
+        "@noble/curves": "^1.3.0",
+        "@noble/hashes": "^1.3.3",
+        "@polkadot/networks": "12.6.2",
+        "@polkadot/util": "12.6.2",
+        "@polkadot/wasm-crypto": "^7.3.2",
+        "@polkadot/wasm-util": "^7.3.2",
+        "@polkadot/x-bigint": "12.6.2",
+        "@polkadot/x-randomvalues": "12.6.2",
+        "@scure/base": "^1.1.5",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "peerDependencies": {
+        "@polkadot/util": "12.6.2"
+      }
+    },
+    "node_modules/@polkadot/wasm-bridge": {
+      "version": "7.3.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/wasm-bridge/-/wasm-bridge-7.3.2.tgz",
+      "integrity": "sha512-AJEXChcf/nKXd5Q/YLEV5dXQMle3UNT7jcXYmIffZAo/KI394a+/24PaISyQjoNC0fkzS1Q8T5pnGGHmXiVz2g==",
+      "dependencies": {
+        "@polkadot/wasm-util": "7.3.2",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "peerDependencies": {
+        "@polkadot/util": "*",
+        "@polkadot/x-randomvalues": "*"
+      }
+    },
+    "node_modules/@polkadot/wasm-crypto": {
+      "version": "7.3.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto/-/wasm-crypto-7.3.2.tgz",
+      "integrity": "sha512-+neIDLSJ6jjVXsjyZ5oLSv16oIpwp+PxFqTUaZdZDoA2EyFRQB8pP7+qLsMNk+WJuhuJ4qXil/7XiOnZYZ+wxw==",
+      "dependencies": {
+        "@polkadot/wasm-bridge": "7.3.2",
+        "@polkadot/wasm-crypto-asmjs": "7.3.2",
+        "@polkadot/wasm-crypto-init": "7.3.2",
+        "@polkadot/wasm-crypto-wasm": "7.3.2",
+        "@polkadot/wasm-util": "7.3.2",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "peerDependencies": {
+        "@polkadot/util": "*",
+        "@polkadot/x-randomvalues": "*"
+      }
+    },
+    "node_modules/@polkadot/wasm-crypto-asmjs": {
+      "version": "7.3.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto-asmjs/-/wasm-crypto-asmjs-7.3.2.tgz",
+      "integrity": "sha512-QP5eiUqUFur/2UoF2KKKYJcesc71fXhQFLT3D4ZjG28Mfk2ZPI0QNRUfpcxVQmIUpV5USHg4geCBNuCYsMm20Q==",
+      "dependencies": {
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "peerDependencies": {
+        "@polkadot/util": "*"
+      }
+    },
+    "node_modules/@polkadot/wasm-crypto-init": {
+      "version": "7.3.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto-init/-/wasm-crypto-init-7.3.2.tgz",
+      "integrity": "sha512-FPq73zGmvZtnuJaFV44brze3Lkrki3b4PebxCy9Fplw8nTmisKo9Xxtfew08r0njyYh+uiJRAxPCXadkC9sc8g==",
+      "dependencies": {
+        "@polkadot/wasm-bridge": "7.3.2",
+        "@polkadot/wasm-crypto-asmjs": "7.3.2",
+        "@polkadot/wasm-crypto-wasm": "7.3.2",
+        "@polkadot/wasm-util": "7.3.2",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "peerDependencies": {
+        "@polkadot/util": "*",
+        "@polkadot/x-randomvalues": "*"
+      }
+    },
+    "node_modules/@polkadot/wasm-crypto-wasm": {
+      "version": "7.3.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto-wasm/-/wasm-crypto-wasm-7.3.2.tgz",
+      "integrity": "sha512-15wd0EMv9IXs5Abp1ZKpKKAVyZPhATIAHfKsyoWCEFDLSOA0/K0QGOxzrAlsrdUkiKZOq7uzSIgIDgW8okx2Mw==",
+      "dependencies": {
+        "@polkadot/wasm-util": "7.3.2",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "peerDependencies": {
+        "@polkadot/util": "*"
+      }
+    },
+    "node_modules/@polkadot/wasm-util": {
+      "version": "7.3.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/wasm-util/-/wasm-util-7.3.2.tgz",
+      "integrity": "sha512-bmD+Dxo1lTZyZNxbyPE380wd82QsX+43mgCm40boyKrRppXEyQmWT98v/Poc7chLuskYb6X8IQ6lvvK2bGR4Tg==",
+      "dependencies": {
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "peerDependencies": {
+        "@polkadot/util": "*"
+      }
+    },
+    "node_modules/@polkadot/x-bigint": {
+      "version": "12.6.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/x-bigint/-/x-bigint-12.6.2.tgz",
+      "integrity": "sha512-HSIk60uFPX4GOFZSnIF7VYJz7WZA7tpFJsne7SzxOooRwMTWEtw3fUpFy5cYYOeLh17/kHH1Y7SVcuxzVLc74Q==",
+      "dependencies": {
+        "@polkadot/x-global": "12.6.2",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@polkadot/x-fetch": {
+      "version": "12.6.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/x-fetch/-/x-fetch-12.6.2.tgz",
+      "integrity": "sha512-8wM/Z9JJPWN1pzSpU7XxTI1ldj/AfC8hKioBlUahZ8gUiJaOF7K9XEFCrCDLis/A1BoOu7Ne6WMx/vsJJIbDWw==",
+      "dependencies": {
+        "@polkadot/x-global": "12.6.2",
+        "node-fetch": "^3.3.2",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@polkadot/x-global": {
+      "version": "12.6.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/x-global/-/x-global-12.6.2.tgz",
+      "integrity": "sha512-a8d6m+PW98jmsYDtAWp88qS4dl8DyqUBsd0S+WgyfSMtpEXu6v9nXDgPZgwF5xdDvXhm+P0ZfVkVTnIGrScb5g==",
+      "dependencies": {
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@polkadot/x-randomvalues": {
+      "version": "12.6.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/x-randomvalues/-/x-randomvalues-12.6.2.tgz",
+      "integrity": "sha512-Vr8uG7rH2IcNJwtyf5ebdODMcr0XjoCpUbI91Zv6AlKVYOGKZlKLYJHIwpTaKKB+7KPWyQrk4Mlym/rS7v9feg==",
+      "dependencies": {
+        "@polkadot/x-global": "12.6.2",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "peerDependencies": {
+        "@polkadot/util": "12.6.2",
+        "@polkadot/wasm-util": "*"
+      }
+    },
+    "node_modules/@polkadot/x-textdecoder": {
+      "version": "12.6.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/x-textdecoder/-/x-textdecoder-12.6.2.tgz",
+      "integrity": "sha512-M1Bir7tYvNappfpFWXOJcnxUhBUFWkUFIdJSyH0zs5LmFtFdbKAeiDXxSp2Swp5ddOZdZgPac294/o2TnQKN1w==",
+      "dependencies": {
+        "@polkadot/x-global": "12.6.2",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@polkadot/x-textencoder": {
+      "version": "12.6.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/x-textencoder/-/x-textencoder-12.6.2.tgz",
+      "integrity": "sha512-4N+3UVCpI489tUJ6cv3uf0PjOHvgGp9Dl+SZRLgFGt9mvxnvpW/7+XBADRMtlG4xi5gaRK7bgl5bmY6OMDsNdw==",
+      "dependencies": {
+        "@polkadot/x-global": "12.6.2",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@polkadot/x-ws": {
+      "version": "12.6.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/x-ws/-/x-ws-12.6.2.tgz",
+      "integrity": "sha512-cGZWo7K5eRRQCRl2LrcyCYsrc3lRbTlixZh3AzgU8uX4wASVGRlNWi/Hf4TtHNe1ExCDmxabJzdIsABIfrr7xw==",
+      "dependencies": {
+        "@polkadot/x-global": "12.6.2",
+        "tslib": "^2.6.2",
+        "ws": "^8.15.1"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@scure/base": {
+      "version": "1.1.5",
+      "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.5.tgz",
+      "integrity": "sha512-Brj9FiG2W1MRQSTB212YVPRrcbjkv48FoZi/u4l/zds/ieRrqsh7aUf6CLwkAq61oKXr/ZlTzlY66gLIj3TFTQ==",
+      "funding": {
+        "url": "https://paulmillr.com/funding/"
+      }
+    },
+    "node_modules/@substrate/connect": {
+      "version": "0.7.35",
+      "resolved": "https://registry.npmjs.org/@substrate/connect/-/connect-0.7.35.tgz",
+      "integrity": "sha512-Io8vkalbwaye+7yXfG1Nj52tOOoJln2bMlc7Q9Yy3vEWqZEVkgKmcPVzbwV0CWL3QD+KMPDA2Dnw/X7EdwgoLw==",
+      "hasInstallScript": true,
+      "optional": true,
+      "dependencies": {
+        "@substrate/connect-extension-protocol": "^1.0.1",
+        "smoldot": "2.0.7"
+      }
+    },
+    "node_modules/@substrate/connect-extension-protocol": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/@substrate/connect-extension-protocol/-/connect-extension-protocol-1.0.1.tgz",
+      "integrity": "sha512-161JhCC1csjH3GE5mPLEd7HbWtwNSPJBg3p1Ksz9SFlTzj/bgEwudiRN2y5i0MoLGCIJRYKyKGMxVnd29PzNjg==",
+      "optional": true
+    },
+    "node_modules/@substrate/ss58-registry": {
+      "version": "1.44.0",
+      "resolved": "https://registry.npmjs.org/@substrate/ss58-registry/-/ss58-registry-1.44.0.tgz",
+      "integrity": "sha512-7lQ/7mMCzVNSEfDS4BCqnRnKCFKpcOaPrxMeGTXHX1YQzM/m2BBHjbK2C3dJvjv7GYxMiaTq/HdWQj1xS6ss+A=="
+    },
+    "node_modules/@types/bn.js": {
+      "version": "5.1.5",
+      "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.5.tgz",
+      "integrity": "sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A==",
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@types/node": {
+      "version": "20.10.5",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.5.tgz",
+      "integrity": "sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw==",
+      "dependencies": {
+        "undici-types": "~5.26.4"
+      }
+    },
+    "node_modules/bn.js": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz",
+      "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ=="
+    },
+    "node_modules/data-uri-to-buffer": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
+      "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
+      "engines": {
+        "node": ">= 12"
+      }
+    },
+    "node_modules/debug": {
+      "version": "4.3.4",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+      "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+      "dependencies": {
+        "ms": "2.1.2"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/eventemitter3": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
+      "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="
+    },
+    "node_modules/fetch-blob": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
+      "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/jimmywarting"
+        },
+        {
+          "type": "paypal",
+          "url": "https://paypal.me/jimmywarting"
+        }
+      ],
+      "dependencies": {
+        "node-domexception": "^1.0.0",
+        "web-streams-polyfill": "^3.0.3"
+      },
+      "engines": {
+        "node": "^12.20 || >= 14.13"
+      }
+    },
+    "node_modules/formdata-polyfill": {
+      "version": "4.0.10",
+      "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
+      "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
+      "dependencies": {
+        "fetch-blob": "^3.1.2"
+      },
+      "engines": {
+        "node": ">=12.20.0"
+      }
+    },
+    "node_modules/json-stringify-safe": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+      "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="
+    },
+    "node_modules/mock-socket": {
+      "version": "9.3.1",
+      "resolved": "https://registry.npmjs.org/mock-socket/-/mock-socket-9.3.1.tgz",
+      "integrity": "sha512-qxBgB7Qa2sEQgHFjj0dSigq7fX4k6Saisd5Nelwp2q8mlbAFh5dHV9JTTlF8viYJLSSWgMCZFUom8PJcMNBoJw==",
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/ms": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+    },
+    "node_modules/nock": {
+      "version": "13.4.0",
+      "resolved": "https://registry.npmjs.org/nock/-/nock-13.4.0.tgz",
+      "integrity": "sha512-W8NVHjO/LCTNA64yxAPHV/K47LpGYcVzgKd3Q0n6owhwvD0Dgoterc25R4rnZbckJEb6Loxz1f5QMuJpJnbSyQ==",
+      "dependencies": {
+        "debug": "^4.1.0",
+        "json-stringify-safe": "^5.0.1",
+        "propagate": "^2.0.0"
+      },
+      "engines": {
+        "node": ">= 10.13"
+      }
+    },
+    "node_modules/node-domexception": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
+      "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/jimmywarting"
+        },
+        {
+          "type": "github",
+          "url": "https://paypal.me/jimmywarting"
+        }
+      ],
+      "engines": {
+        "node": ">=10.5.0"
+      }
+    },
+    "node_modules/node-fetch": {
+      "version": "3.3.2",
+      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
+      "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
+      "dependencies": {
+        "data-uri-to-buffer": "^4.0.0",
+        "fetch-blob": "^3.1.4",
+        "formdata-polyfill": "^4.0.10"
+      },
+      "engines": {
+        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/node-fetch"
+      }
+    },
+    "node_modules/propagate": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz",
+      "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==",
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/rxjs": {
+      "version": "7.8.1",
+      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
+      "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
+      "dependencies": {
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/smoldot": {
+      "version": "2.0.7",
+      "resolved": "https://registry.npmjs.org/smoldot/-/smoldot-2.0.7.tgz",
+      "integrity": "sha512-VAOBqEen6vises36/zgrmAT1GWk2qE3X8AGnO7lmQFdskbKx8EovnwS22rtPAG+Y1Rk23/S22kDJUdPANyPkBA==",
+      "optional": true,
+      "dependencies": {
+        "ws": "^8.8.1"
+      }
+    },
+    "node_modules/tslib": {
+      "version": "2.6.2",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+      "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+    },
+    "node_modules/undici-types": {
+      "version": "5.26.5",
+      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
+      "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
+    },
+    "node_modules/web-streams-polyfill": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz",
+      "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==",
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/ws": {
+      "version": "8.16.0",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz",
+      "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==",
+      "engines": {
+        "node": ">=10.0.0"
+      },
+      "peerDependencies": {
+        "bufferutil": "^4.0.1",
+        "utf-8-validate": ">=5.0.2"
+      },
+      "peerDependenciesMeta": {
+        "bufferutil": {
+          "optional": true
+        },
+        "utf-8-validate": {
+          "optional": true
+        }
+      }
+    }
+  }
+}
diff --git a/bridges/testing/framework/utils/generate_hex_encoded_call/package.json b/bridges/testing/framework/utils/generate_hex_encoded_call/package.json
new file mode 100644
index 00000000000..ecf0a2483db
--- /dev/null
+++ b/bridges/testing/framework/utils/generate_hex_encoded_call/package.json
@@ -0,0 +1,11 @@
+{
+  "name": "y",
+  "version": "y",
+  "description": "create a scale hex-encoded call values from given message",
+  "main": "index.js",
+  "license": "MIT",
+  "dependencies": {
+    "@polkadot/api": "^10.11",
+    "@polkadot/util": "^12.6"
+  }
+}
diff --git a/bridges/testing/framework/utils/zombienet.sh b/bridges/testing/framework/utils/zombienet.sh
new file mode 100644
index 00000000000..bbcd1a30620
--- /dev/null
+++ b/bridges/testing/framework/utils/zombienet.sh
@@ -0,0 +1,39 @@
+#!/bin/bash
+
+source "${BASH_SOURCE%/*}/common.sh"
+
+function start_zombienet() {
+    local test_dir=$1
+    local definition_path=$2
+    local __zombienet_dir=$3
+    local __zombienet_pid=$4
+
+    local zombienet_name=`basename $definition_path .toml`
+    local zombienet_dir=$test_dir/$zombienet_name
+    eval $__zombienet_dir="'$zombienet_dir'"
+    mkdir -p $zombienet_dir
+    rm -rf $zombienet_dir
+
+    local logs_dir=$test_dir/logs
+    mkdir -p $logs_dir
+    local zombienet_log=$logs_dir/$zombienet_name.log
+
+    echo "Starting $zombienet_name zombienet. Logs available at: $zombienet_log"
+    start_background_process \
+        "$ZOMBIENET_BINARY spawn --dir $zombienet_dir --provider native $definition_path" \
+        "$zombienet_log" zombienet_pid
+
+    ensure_process_file $zombienet_pid "$zombienet_dir/zombie.json" 180
+    echo "$zombienet_name zombienet started successfully"
+
+    eval $__zombienet_pid="'$zombienet_pid'"
+}
+
+function run_zndsl() {
+    local zndsl_file=$1
+    local zombienet_dir=$2
+
+    echo "Running $zndsl_file."
+    $ZOMBIENET_BINARY test --dir $zombienet_dir --provider native $zndsl_file $zombienet_dir/zombie.json
+    echo
+}
diff --git a/bridges/testing/run-new-test.sh b/bridges/testing/run-new-test.sh
new file mode 100755
index 00000000000..7c84a69aa47
--- /dev/null
+++ b/bridges/testing/run-new-test.sh
@@ -0,0 +1,48 @@
+#!/bin/bash
+
+set -e
+
+trap 'kill -9 -$$ || echo "Environment already teared down"' SIGINT SIGTERM EXIT
+
+test=$1
+shift
+
+# whether to use paths for zombienet+bridges tests container or for local testing
+ZOMBIENET_DOCKER_PATHS=0
+while [ $# -ne 0 ]
+do
+    arg="$1"
+    case "$arg" in
+        --docker)
+            ZOMBIENET_DOCKER_PATHS=1
+            ;;
+    esac
+    shift
+done
+
+export POLKADOT_SDK_PATH=`realpath ${BASH_SOURCE%/*}/../..`
+export FRAMEWORK_PATH=`realpath ${BASH_SOURCE%/*}/framework`
+
+# set path to binaries
+if [ "$ZOMBIENET_DOCKER_PATHS" -eq 1 ]; then
+    # otherwise zombienet uses some hardcoded paths
+    unset RUN_IN_CONTAINER
+    unset ZOMBIENET_IMAGE
+
+    export POLKADOT_BINARY=/usr/local/bin/polkadot
+    export POLKADOT_PARACHAIN_BINARY=/usr/local/bin/polkadot-parachain
+
+    export ZOMBIENET_BINARY=/usr/local/bin/zombie
+    export SUBSTRATE_RELAY_BINARY=/usr/local/bin/substrate-relay
+else
+    export POLKADOT_BINARY=$POLKADOT_SDK_PATH/target/release/polkadot
+    export POLKADOT_PARACHAIN_BINARY=$POLKADOT_SDK_PATH/target/release/polkadot-parachain
+
+    export ZOMBIENET_BINARY=~/local_bridge_testing/bin/zombienet-linux-x64
+    export SUBSTRATE_RELAY_BINARY=~/local_bridge_testing/bin/substrate-relay
+fi
+
+export TEST_DIR=`mktemp -d /tmp/bridges-tests-run-XXXXX`
+echo -e "Test folder: $TEST_DIR\n"
+
+${BASH_SOURCE%/*}/tests/$test/run.sh
diff --git a/bridges/testing/run-tests.sh b/bridges/testing/run-tests.sh
new file mode 100755
index 00000000000..6149d991265
--- /dev/null
+++ b/bridges/testing/run-tests.sh
@@ -0,0 +1,138 @@
+#!/bin/bash
+set -x
+shopt -s nullglob
+
+trap "trap - SIGINT SIGTERM EXIT && killall -q -9 substrate-relay && kill -- -$$" SIGINT SIGTERM EXIT
+
+# run tests in range [TESTS_BEGIN; TESTS_END)
+TESTS_BEGIN=1
+TESTS_END=1000
+# whether to use paths for zombienet+bridges tests container or for local testing
+ZOMBIENET_DOCKER_PATHS=0
+while [ $# -ne 0 ]
+do
+    arg="$1"
+    case "$arg" in
+        --docker)
+            ZOMBIENET_DOCKER_PATHS=1
+            ;;
+        --test)
+            shift
+            TESTS_BEGIN="$1"
+            TESTS_END="$1"
+            ;;
+    esac
+    shift
+done
+
+# assuming that we'll be using native provide && all processes will be executing locally
+# (we need absolute paths here, because they're used when scripts are called by zombienet from tmp folders)
+export POLKADOT_SDK_PATH=`realpath $(dirname "$0")/../..`
+export BRIDGE_TESTS_FOLDER=$POLKADOT_SDK_PATH/bridges/testing/tests
+
+# set pathc to binaries
+if [ "$ZOMBIENET_DOCKER_PATHS" -eq 1 ]; then
+    export POLKADOT_BINARY=/usr/local/bin/polkadot
+    export POLKADOT_PARACHAIN_BINARY=/usr/local/bin/polkadot-parachain
+
+    export SUBSTRATE_RELAY_BINARY=/usr/local/bin/substrate-relay
+    export ZOMBIENET_BINARY_PATH=/usr/local/bin/zombie
+else
+    export POLKADOT_BINARY=$POLKADOT_SDK_PATH/target/release/polkadot
+    export POLKADOT_PARACHAIN_BINARY=$POLKADOT_SDK_PATH/target/release/polkadot-parachain
+
+    export SUBSTRATE_RELAY_BINARY=~/local_bridge_testing/bin/substrate-relay
+    export ZOMBIENET_BINARY_PATH=~/local_bridge_testing/bin/zombienet-linux
+fi
+
+# check if `wait` supports -p flag
+if [ `printf "$BASH_VERSION\n5.1" | sort -V | head -n 1` = "5.1" ]; then IS_BASH_5_1=1; else IS_BASH_5_1=0; fi
+
+# bridge configuration
+export LANE_ID="00000002"
+
+# tests configuration
+ALL_TESTS_FOLDER=`mktemp -d /tmp/bridges-zombienet-tests.XXXXX`
+
+function start_coproc() {
+    local command=$1
+    local name=$2
+    local logname=`basename $name`
+    local coproc_log=`mktemp -p $TEST_FOLDER $logname.XXXXX`
+    coproc COPROC {
+        # otherwise zombienet uses some hardcoded paths
+        unset RUN_IN_CONTAINER
+        unset ZOMBIENET_IMAGE
+
+        $command >$coproc_log 2>&1
+    }
+    TEST_COPROCS[$COPROC_PID, 0]=$name
+    TEST_COPROCS[$COPROC_PID, 1]=$coproc_log
+    echo "Spawned $name coprocess. StdOut + StdErr: $coproc_log"
+
+    return $COPROC_PID
+}
+
+# execute every test from tests folder
+TEST_INDEX=$TESTS_BEGIN
+while true
+do
+    declare -A TEST_COPROCS
+    TEST_COPROCS_COUNT=0
+    TEST_PREFIX=$(printf "%04d" $TEST_INDEX)
+
+    # it'll be used by the `sync-exit.sh` script
+    export TEST_FOLDER=`mktemp -d -p $ALL_TESTS_FOLDER test-$TEST_PREFIX.XXXXX`
+
+    # check if there are no more tests
+    zndsl_files=($BRIDGE_TESTS_FOLDER/$TEST_PREFIX-*.zndsl)
+    if [ ${#zndsl_files[@]} -eq 0 ]; then
+        break
+    fi
+
+    # start tests
+    for zndsl_file in "${zndsl_files[@]}"; do
+        start_coproc "$ZOMBIENET_BINARY_PATH --provider native test $zndsl_file" "$zndsl_file"
+        echo -n "1">>$TEST_FOLDER/exit-sync
+        ((TEST_COPROCS_COUNT++))
+    done
+    # wait until all tests are completed
+    for n in `seq 1 $TEST_COPROCS_COUNT`; do
+        if [ "$IS_BASH_5_1" -eq 1 ]; then
+            wait -n -p COPROC_PID
+            exit_code=$?
+            coproc_name=${TEST_COPROCS[$COPROC_PID, 0]}
+            coproc_log=${TEST_COPROCS[$COPROC_PID, 1]}
+            coproc_stdout=$(cat $coproc_log)
+        else
+            wait -n
+            exit_code=$?
+            coproc_name="<unknown>"
+            coproc_stdout="<unknown>"
+        fi
+        echo "Process $coproc_name has finished with exit code: $exit_code"
+
+        # if exit code is not zero, exit
+        if [ $exit_code -ne 0 ]; then
+            echo "====================================================================="
+            echo "=== Shutting down. Log of failed process below                    ==="
+            echo "====================================================================="
+            echo "$coproc_stdout"
+
+            exit 1
+        fi
+    done
+
+    # proceed to next index
+    ((TEST_INDEX++))
+    if [ "$TEST_INDEX" -ge "$TESTS_END" ]; then
+        break
+    fi
+
+    # kill relay here - it is started manually by tests
+    killall substrate-relay
+done
+
+echo "====================================================================="
+echo "=== All tests have completed successfully                         ==="
+echo "====================================================================="
diff --git a/bridges/testing/scripts/invoke-script.sh b/bridges/testing/scripts/invoke-script.sh
new file mode 100755
index 00000000000..cd0557b071b
--- /dev/null
+++ b/bridges/testing/scripts/invoke-script.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+INVOKE_LOG=`mktemp -p $TEST_FOLDER invoke.XXXXX`
+
+pushd $POLKADOT_SDK_PATH/bridges/testing/environments/rococo-westend
+./bridges_rococo_westend.sh $1 >$INVOKE_LOG 2>&1
+popd
diff --git a/bridges/testing/scripts/start-relayer.sh b/bridges/testing/scripts/start-relayer.sh
new file mode 100755
index 00000000000..38ea62fad52
--- /dev/null
+++ b/bridges/testing/scripts/start-relayer.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+RELAY_LOG=`mktemp -p $TEST_FOLDER relay.XXXXX`
+
+pushd $POLKADOT_SDK_PATH/bridges/testing/environments/rococo-westend
+./bridges_rococo_westend.sh run-relay >$RELAY_LOG 2>&1&
+popd
diff --git a/bridges/testing/scripts/sync-exit.sh b/bridges/testing/scripts/sync-exit.sh
new file mode 100755
index 00000000000..cc20b098e78
--- /dev/null
+++ b/bridges/testing/scripts/sync-exit.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+set -e
+
+# every network adds a char to the file, let's remove ours
+truncate -s -1 $TEST_FOLDER/exit-sync
+
+# when all chars are removed, then our test is done
+while true
+do
+    if [ `stat --printf="%s" $TEST_FOLDER/exit-sync` -eq 0 ]; then
+        exit
+    fi
+    sleep 100
+done
diff --git a/bridges/testing/tests/0001-asset-transfer/roc-reaches-westend.zndsl b/bridges/testing/tests/0001-asset-transfer/roc-reaches-westend.zndsl
new file mode 100644
index 00000000000..cdb7d28e940
--- /dev/null
+++ b/bridges/testing/tests/0001-asset-transfer/roc-reaches-westend.zndsl
@@ -0,0 +1,12 @@
+Description: User is able to transfer ROC from Rococo Asset Hub to Westend Asset Hub and back
+Network: {{ENV_PATH}}/bridge_hub_westend_local_network.toml
+Creds: config
+
+# send 5 ROC to //Alice from Rococo AH to Westend AH
+asset-hub-westend-collator1: run {{ENV_PATH}}/helper.sh with "reserve-transfer-assets-from-asset-hub-rococo-local 5000000000000" within 120 seconds
+
+# check that //Alice received at least 4.8 ROC on Westend AH
+asset-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/wrapped-assets-balance.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY,4800000000000,Rococo" within 600 seconds
+
+# check that the relayer //Charlie is rewarded by Westend AH
+bridge-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/relayer-rewards.js with "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y,0x00000002,0x6268726F,ThisChain,0" within 30 seconds
diff --git a/bridges/testing/tests/0001-asset-transfer/run.sh b/bridges/testing/tests/0001-asset-transfer/run.sh
new file mode 100755
index 00000000000..a7bb122919b
--- /dev/null
+++ b/bridges/testing/tests/0001-asset-transfer/run.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+set -e
+
+source "${BASH_SOURCE%/*}/../../framework/utils/common.sh"
+source "${BASH_SOURCE%/*}/../../framework/utils/zombienet.sh"
+
+export ENV_PATH=`realpath ${BASH_SOURCE%/*}/../../environments/rococo-westend`
+
+$ENV_PATH/spawn.sh --init --start-relayer &
+env_pid=$!
+
+ensure_process_file $env_pid $TEST_DIR/rococo.env 600
+rococo_dir=`cat $TEST_DIR/rococo.env`
+echo
+
+ensure_process_file $env_pid $TEST_DIR/westend.env 300
+westend_dir=`cat $TEST_DIR/westend.env`
+echo
+
+run_zndsl ${BASH_SOURCE%/*}/roc-reaches-westend.zndsl $westend_dir
+run_zndsl ${BASH_SOURCE%/*}/wnd-reaches-rococo.zndsl $rococo_dir
+
+run_zndsl ${BASH_SOURCE%/*}/wroc-reaches-rococo.zndsl $rococo_dir
+run_zndsl ${BASH_SOURCE%/*}/wwnd-reaches-westend.zndsl $westend_dir
diff --git a/bridges/testing/tests/0001-asset-transfer/wnd-reaches-rococo.zndsl b/bridges/testing/tests/0001-asset-transfer/wnd-reaches-rococo.zndsl
new file mode 100644
index 00000000000..dbc03864e2b
--- /dev/null
+++ b/bridges/testing/tests/0001-asset-transfer/wnd-reaches-rococo.zndsl
@@ -0,0 +1,12 @@
+Description: User is able to transfer WND from Westend Asset Hub to Rococo Asset Hub and back
+Network: {{ENV_PATH}}/bridge_hub_rococo_local_network.toml
+Creds: config
+
+# send 5 WND to //Alice from Westend AH to Rococo AH
+asset-hub-rococo-collator1: run {{ENV_PATH}}/helper.sh with "reserve-transfer-assets-from-asset-hub-westend-local 5000000000000" within 120 seconds
+
+# check that //Alice received at least 4.8 WND on Rococo AH
+asset-hub-rococo-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/wrapped-assets-balance.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY,4800000000000,Westend" within 600 seconds
+
+# check that the relayer //Charlie is rewarded by Rococo AH
+bridge-hub-rococo-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/relayer-rewards.js with "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y,0x00000002,0x62687764,ThisChain,0" within 30 seconds
diff --git a/bridges/testing/tests/0001-asset-transfer/wroc-reaches-rococo.zndsl b/bridges/testing/tests/0001-asset-transfer/wroc-reaches-rococo.zndsl
new file mode 100644
index 00000000000..9967732cabe
--- /dev/null
+++ b/bridges/testing/tests/0001-asset-transfer/wroc-reaches-rococo.zndsl
@@ -0,0 +1,10 @@
+Description: User is able to transfer ROC from Rococo Asset Hub to Westend Asset Hub and back
+Network: {{ENV_PATH}}/bridge_hub_westend_local_network.toml
+Creds: config
+
+# send 3 wROC back to Alice from Westend AH to Rococo AH
+asset-hub-rococo-collator1: run {{ENV_PATH}}/helper.sh with "withdraw-reserve-assets-from-asset-hub-westend-local 3000000000000" within 120 seconds
+
+# check that //Alice received at least 2.8 wROC on Rococo AH
+# (we wait until //Alice account increases here - there are no other transactions that may increase it)
+asset-hub-rococo-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/native-assets-balance-increased.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY,2800000000000" within 600 seconds
diff --git a/bridges/testing/tests/0001-asset-transfer/wwnd-reaches-westend.zndsl b/bridges/testing/tests/0001-asset-transfer/wwnd-reaches-westend.zndsl
new file mode 100644
index 00000000000..2037b0baf3c
--- /dev/null
+++ b/bridges/testing/tests/0001-asset-transfer/wwnd-reaches-westend.zndsl
@@ -0,0 +1,10 @@
+Description: User is able to transfer ROC from Rococo Asset Hub to Westend Asset Hub and back
+Network: {{ENV_PATH}}/bridge_hub_westend_local_network.toml
+Creds: config
+
+# send 3 wWND back to Alice from Rococo AH to Westend AH
+asset-hub-westend-collator1: run {{ENV_PATH}}/helper.sh with "withdraw-reserve-assets-from-asset-hub-rococo-local 3000000000000" within 120 seconds
+
+# check that //Alice received at least 2.8 wWND on Westend AH
+# (we wait until //Alice account increases here - there are no other transactions that may increase it)
+asset-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/native-assets-balance-increased.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY,2800000000000" within 600 seconds
diff --git a/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/rococo-to-westend.zndsl b/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/rococo-to-westend.zndsl
new file mode 100644
index 00000000000..6e381f53773
--- /dev/null
+++ b/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/rococo-to-westend.zndsl
@@ -0,0 +1,8 @@
+Description: While relayer is idle, we only sync mandatory Rococo (and a single Rococo BH) headers to Westend BH.
+Network: {{ENV_PATH}}/bridge_hub_westend_local_network.toml
+Creds: config
+
+# ensure that relayer is only syncing mandatory headers while idle. This includes both headers that were
+# generated while relay was offline and those in the next 100 seconds while script is active.
+bridge-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/only-mandatory-headers-synced-when-idle.js with "300,rococo-at-westend" within 600 seconds
+
diff --git a/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/run.sh b/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/run.sh
new file mode 100755
index 00000000000..7d5b8d92736
--- /dev/null
+++ b/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/run.sh
@@ -0,0 +1,35 @@
+#!/bin/bash
+
+set -e
+
+source "${BASH_SOURCE%/*}/../../framework/utils/common.sh"
+source "${BASH_SOURCE%/*}/../../framework/utils/zombienet.sh"
+
+export ENV_PATH=`realpath ${BASH_SOURCE%/*}/../../environments/rococo-westend`
+
+$ENV_PATH/spawn.sh &
+env_pid=$!
+
+ensure_process_file $env_pid $TEST_DIR/rococo.env 600
+rococo_dir=`cat $TEST_DIR/rococo.env`
+echo
+
+ensure_process_file $env_pid $TEST_DIR/westend.env 300
+westend_dir=`cat $TEST_DIR/westend.env`
+echo
+
+# Sleep for some time before starting the relayer. We want to sleep for at least 1 session,
+# which is expected to be 60 seconds for the test environment.
+echo -e "Sleeping 90s before starting relayer ...\n"
+sleep 90
+${BASH_SOURCE%/*}/../../environments/rococo-westend/start_relayer.sh $rococo_dir $westend_dir relayer_pid
+
+# Sometimes the relayer syncs multiple parachain heads in the begining leading to test failures.
+# See issue: https://github.com/paritytech/parity-bridges-common/issues/2838.
+# TODO: Remove this sleep after the issue is fixed.
+echo -e "Sleeping 180s before runing the tests ...\n"
+sleep 180
+
+run_zndsl ${BASH_SOURCE%/*}/rococo-to-westend.zndsl $westend_dir
+run_zndsl ${BASH_SOURCE%/*}/westend-to-rococo.zndsl $rococo_dir
+
diff --git a/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/westend-to-rococo.zndsl b/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/westend-to-rococo.zndsl
new file mode 100644
index 00000000000..b4b3e436791
--- /dev/null
+++ b/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/westend-to-rococo.zndsl
@@ -0,0 +1,7 @@
+Description: While relayer is idle, we only sync mandatory Westend (and a single Westend BH) headers to Rococo BH.
+Network: {{ENV_PATH}}/bridge_hub_rococo_local_network.toml
+Creds: config
+
+# ensure that relayer is only syncing mandatory headers while idle. This includes both headers that were
+# generated while relay was offline and those in the next 100 seconds while script is active.
+bridge-hub-rococo-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/only-mandatory-headers-synced-when-idle.js with "300,westend-at-rococo" within 600 seconds
diff --git a/bridges/testing/tests/0003-required-headers-synced-while-active-rococo-to-westend.zndsl b/bridges/testing/tests/0003-required-headers-synced-while-active-rococo-to-westend.zndsl
new file mode 100644
index 00000000000..07b91481dc7
--- /dev/null
+++ b/bridges/testing/tests/0003-required-headers-synced-while-active-rococo-to-westend.zndsl
@@ -0,0 +1,26 @@
+Description: While relayer is active, we only sync mandatory and required Rococo (and Rococo BH) headers to Westend BH.
+Network: ../environments/rococo-westend/bridge_hub_westend_local_network.toml
+Creds: config
+
+# step 1: initialize Westend AH
+asset-hub-westend-collator1: run ../scripts/invoke-script.sh with "init-asset-hub-westend-local" within 60 seconds
+
+# step 2: initialize Westend bridge hub
+bridge-hub-westend-collator1: run ../scripts/invoke-script.sh with "init-bridge-hub-westend-local" within 60 seconds
+
+# step 3: ensure that initialization has completed
+asset-hub-westend-collator1: js-script ../js-helpers/wait-hrmp-channel-opened.js with "1002" within 600 seconds
+
+# step 4: send message from Westend to Rococo
+asset-hub-westend-collator1: run ../scripts/invoke-script.sh with "reserve-transfer-assets-from-asset-hub-westend-local" within 60 seconds
+
+# step 5: start relayer
+# (we are starting it after sending the message to be sure that relayer won't relay messages before our js script
+# will be started at step 6)
+# (it is started by sibling 0003-required-headers-synced-while-active-westend-to-rococo.zndsl)
+
+# step 6: ensure that relayer won't sync any extra headers while delivering messages and confirmations
+bridge-hub-westend-collator1: js-script ../js-helpers/only-required-headers-synced-when-active.js with "500,rococo-at-westend" within 600 seconds
+
+# wait until other network test has completed OR exit with an error too
+asset-hub-westend-collator1: run ../scripts/sync-exit.sh within 600 seconds
diff --git a/bridges/testing/tests/0003-required-headers-synced-while-active-westend-to-rococo.zndsl b/bridges/testing/tests/0003-required-headers-synced-while-active-westend-to-rococo.zndsl
new file mode 100644
index 00000000000..a6b11fc2405
--- /dev/null
+++ b/bridges/testing/tests/0003-required-headers-synced-while-active-westend-to-rococo.zndsl
@@ -0,0 +1,26 @@
+Description: While relayer is active, we only sync mandatory and required Westend (and Westend BH) headers to Rococo BH.
+Network: ../environments/rococo-westend/bridge_hub_rococo_local_network.toml
+Creds: config
+
+# step 1: initialize Rococo AH
+asset-hub-rococo-collator1: run ../scripts/invoke-script.sh with "init-asset-hub-rococo-local" within 60 seconds
+
+# step 2: initialize Rococo bridge hub
+bridge-hub-rococo-collator1: run ../scripts/invoke-script.sh with "init-bridge-hub-rococo-local" within 60 seconds
+
+# step 3: ensure that initialization has completed
+asset-hub-rococo-collator1: js-script ../js-helpers/wait-hrmp-channel-opened.js with "1013" within 600 seconds
+
+# step 4: send message from Rococo to Westend
+asset-hub-rococo-collator1: run ../scripts/invoke-script.sh with "reserve-transfer-assets-from-asset-hub-rococo-local" within 60 seconds
+
+# step 5: start relayer
+# (we are starting it after sending the message to be sure that relayer won't relay messages before our js script
+# will be started at step 6)
+bridge-hub-rococo-collator1: run ../scripts/start-relayer.sh within 60 seconds
+
+# step 6: ensure that relayer won't sync any extra headers while delivering messages and confirmations
+bridge-hub-rococo-collator1: js-script ../js-helpers/only-required-headers-synced-when-active.js with "500,westend-at-rococo" within 600 seconds
+
+# wait until other network test has completed OR exit with an error too
+asset-hub-rococo-collator1: run ../scripts/sync-exit.sh within 600 seconds
-- 
GitLab