Repository: LedgerHQ/app-ethereum (language: python)

Issue:
Fix potential out-of-bounds read by up to 2 bytes during TLV parsing
## Description

Fix issue identified in security review.

## Changes include

- [x] Bugfix (non-breaking change that solves an issue)
- [ ] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (change that is not backwards-compatible and/or changes current functionality)
- [ ] Tests
- [ ] Documentation
- [ ] Other (for changes that might not fit in any category)


Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2f020aeda..dfd5448ce 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,31 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](http://keepachangelog.com/)
 and this project adheres to [Semantic Versioning](http://semver.org/).
 
+## [1.13.0](https://github.com/ledgerhq/app-ethereum/compare/1.12.2...1.13.0) - 2024-11-26
+
+### Added
+
+- (clone) IoTeX
+- (network) Defi Oracle Meta
+- (network) IoTeX
+- (network) IoTeX Testnet
+- (network) Neo X Mainnet
+- (network) Neo X Testnet
+- (network) Bitlayer
+- (network) Bitlayer Testnet
+- Dynamic network handling, can get new networks at runtime from the CAL instead of from the hardcoded list
+- Support for Ethermint's non-standard EIP-712 verifyingContract
+
+### Changed
+
+- Improved error handling in swap mode
+- Provide NFT info APDU does not require a loaded NFT (721/1155) internal plugin anymore
+
+### Fixed
+
+- Potential overflow on the UI buffer used for amounts
+- RLP parsing issue with legacy transactions
+
 ## [1.12.2](https://github.com/ledgerhq/app-ethereum/compare/1.12.1...1.12.2) - 2024-10-24
 
 ### Fixed
diff --git a/Makefile b/Makefile
index 781d05d37..12d3243bc 100644
--- a/Makefile
+++ b/Makefile
@@ -36,8 +36,8 @@ endif
 include ./makefile_conf/chain/$(CHAIN).mk
 
 APPVERSION_M = 1
-APPVERSION_N = 12
-APPVERSION_P = 2
+APPVERSION_N = 13
+APPVERSION_P = 0
 APPVERSION = $(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P)
 
 # Application source files
diff --git a/client/src/ledger_app_clients/ethereum/client.py b/client/src/ledger_app_clients/ethereum/client.py
index 9b6a4de89..11325fac4 100644
--- a/client/src/ledger_app_clients/ethereum/client.py
+++ b/client/src/ledger_app_clients/ethereum/client.py
@@ -12,6 +12,7 @@
 from .keychain import sign_data, Key
 from .tlv import format_tlv
 
+from hashlib import sha256
 from web3 import Web3
 
 
@@ -43,21 +44,25 @@ class TrustedNameSource(IntEnum):
     DNS = 0x05
 
 
-class TrustedNameTag(IntEnum):
+class FieldTag(IntEnum):
     STRUCT_TYPE = 0x01
     STRUCT_VERSION = 0x02
     NOT_VALID_AFTER = 0x10
     CHALLENGE = 0x12
     SIGNER_KEY_ID = 0x13
     SIGNER_ALGO = 0x14
-    SIGNATURE = 0x15
-    NAME = 0x20
+    DER_SIGNATURE = 0x15
+    TRUSTED_NAME = 0x20
     COIN_TYPE = 0x21
     ADDRESS = 0x22
     CHAIN_ID = 0x23
-    NAME_TYPE = 0x70
-    NAME_SOURCE = 0x71
-    NFT_ID = 0x72
+    TICKER = 0x24
+    BLOCKCHAIN_FAMILY = 0x51
+    NETWORK_NAME = 0x52
+    NETWORK_ICON_HASH = 0x53
+    TRUSTED_NAME_TYPE = 0x70
+    TRUSTED_NAME_SOURCE = 0x71
+    TRUSTED_NAME_NFT_ID = 0x72
 
 
 class PKIPubKeyUsage(IntEnum):
@@ -266,10 +271,10 @@ def _provide_trusted_name_common(self, payload: bytes) -> RAPDU:
             # pylint: enable=line-too-long
 
             self._pki_client.send_certificate(PKIPubKeyUsage.PUBKEY_USAGE_COIN_META, bytes.fromhex(cert_apdu))
-        payload += format_tlv(TrustedNameTag.STRUCT_TYPE, 3)  # TrustedName
-        payload += format_tlv(TrustedNameTag.SIGNER_KEY_ID, 0)  # test key
-        payload += format_tlv(TrustedNameTag.SIGNER_ALGO, 1)  # secp256k1
-        payload += format_tlv(TrustedNameTag.SIGNATURE,
+        payload += format_tlv(FieldTag.STRUCT_TYPE, 3)  # TrustedName
+        payload += format_tlv(FieldTag.SIGNER_KEY_ID, 0)  # test key
+        payload += format_tlv(FieldTag.SIGNER_ALGO, 1)  # secp256k1
+        payload += format_tlv(FieldTag.DER_SIGNATURE,
                               sign_data(Key.TRUSTED_NAME, payload))
         chunks = self._cmd_builder.provide_trusted_name(payload)
         for chunk in chunks[:-1]:
@@ -277,11 +282,11 @@ def _provide_trusted_name_common(self, payload: bytes) -> RAPDU:
         return self._exchange(chunks[-1])
 
     def provide_trusted_name_v1(self, addr: bytes, name: str, challenge: int) -> RAPDU:
-        payload = format_tlv(TrustedNameTag.STRUCT_VERSION, 1)
-        payload += format_tlv(TrustedNameTag.CHALLENGE, challenge)
-        payload += format_tlv(TrustedNameTag.COIN_TYPE, 0x3c)  # ETH in slip-44
-        payload += format_tlv(TrustedNameTag.NAME, name)
-        payload += format_tlv(TrustedNameTag.ADDRESS, addr)
+        payload = format_tlv(FieldTag.STRUCT_VERSION, 1)
+        payload += format_tlv(FieldTag.CHALLENGE, challenge)
+        payload += format_tlv(FieldTag.COIN_TYPE, 0x3c)  # ETH in slip-44
+        payload += format_tlv(FieldTag.TRUSTED_NAME, name)
+        payload += format_tlv(FieldTag.ADDRESS, addr)
         return self._provide_trusted_name_common(payload)
 
     def provide_trusted_name_v2(self,
@@ -293,19 +298,19 @@ def provide_trusted_name_v2(self,
                                 nft_id: Optional[int] = None,
                                 challenge: Optional[int] = None,
                                 not_valid_after: Optional[tuple[int]] = None) -> RAPDU:
-        payload = format_tlv(TrustedNameTag.STRUCT_VERSION, 2)
-        payload += format_tlv(TrustedNameTag.NAME, name)
-        payload += format_tlv(TrustedNameTag.ADDRESS, addr)
-        payload += format_tlv(TrustedNameTag.NAME_TYPE, name_type)
-        payload += format_tlv(TrustedNameTag.NAME_SOURCE, name_source)
-        payload += format_tlv(TrustedNameTag.CHAIN_ID, chain_id)
+        payload = format_tlv(FieldTag.STRUCT_VERSION, 2)
+        payload += format_tlv(FieldTag.TRUSTED_NAME, name)
+        payload += format_tlv(FieldTag.ADDRESS, addr)
+        payload += format_tlv(FieldTag.TRUSTED_NAME_TYPE, name_type)
+        payload += format_tlv(FieldTag.TRUSTED_NAME_SOURCE, name_source)
+        payload += format_tlv(FieldTag.CHAIN_ID, chain_id)
         if nft_id is not None:
-            payload += format_tlv(TrustedNameTag.NFT_ID, nft_id)
+            payload += format_tlv(FieldTag.TRUSTED_NAME_NFT_ID, nft_id)
         if challenge is not None:
-            payload += format_tlv(TrustedNameTag.CHALLENGE, challenge)
+            payload += format_tlv(FieldTag.CHALLENGE, challenge)
         if not_valid_after is not None:
             assert len(not_valid_after) == 3
-            payload += format_tlv(TrustedNameTag.NOT_VALID_AFTER, struct.pack("BBB", *not_valid_after))
+            payload += format_tlv(FieldTag.NOT_VALID_AFTER, struct.pack("BBB", *not_valid_after))
         return self._provide_trusted_name_common(payload)
 
     def set_plugin(self,
@@ -468,3 +473,54 @@ def provide_token_metadata(self,
                                                                                 decimals,
                                                                                 chain_id,
                                                                                 sig))
+
+    def _prepare_network_info(self,
+                              name: str,
+                              ticker: str,
+                              chain_id: int,
+                              icon: Optional[bytes] = None) -> bytes:
+
+        payload = format_tlv(FieldTag.STRUCT_TYPE, 8)
+        payload += format_tlv(FieldTag.STRUCT_VERSION, 1)
+        payload += format_tlv(FieldTag.BLOCKCHAIN_FAMILY, 1)
+        payload += format_tlv(FieldTag.CHAIN_ID, chain_id.to_bytes(8, 'big'))
+        payload += format_tlv(FieldTag.NETWORK_NAME, name.encode('utf-8'))
+        payload += format_tlv(FieldTag.TICKER, ticker.encode('utf-8'))
+        if icon:
+            # Network Icon
+            payload += format_tlv(FieldTag.NETWORK_ICON_HASH, sha256(icon).digest())
+        # Append the data Signature
+        payload += format_tlv(FieldTag.DER_SIGNATURE,
+                              sign_data(Key.CAL, payload))
+        return payload
+
+    def provide_network_information(self,
+                                    name: str,
+                                    ticker: str,
+                                    chain_id: int,
+                                    icon: Optional[bytes] = None) -> RAPDU:
+
+        if self._pki_client is None:
+            print(f"Ledger-PKI Not supported on '{self._firmware.name}'")
+        else:
+            # pylint: disable=line-too-long
+            if self._firmware == Firmware.NANOSP:
+                cert_apdu = "01010102010211040000000212010013020002140101160400000000200B45524332305F546F6B656E300200063101083201213321024CCA8FAD496AA5040A00A7EB2F5CC3B85376D88BA147A7D7054A99C64056188734010135010310040102000015473045022100C15795C2AE41E6FAE6B1362EE1AE216428507D7C1D6939B928559CC7A1F6425C02206139CF2E133DD62F3E00F183E42109C9853AC62B6B70C5079B9A80DBB9D54AB5"  # noqa: E501
+            elif self._firmware == Firmware.NANOX:
+                cert_apdu = "01010102010211040000000212010013020002140101160400000000200B45524332305F546F6B656E300200063101083201213321024CCA8FAD496AA5040A00A7EB2F5CC3B85376D88BA147A7D7054A99C64056188734010135010215473045022100E3B956F93FBFF0D41908483888F0F75D4714662A692F7A38DC6C41A13294F9370220471991BECB3CA4F43413CADC8FF738A8CC03568BFA832B4DCFE8C469080984E5"  # noqa: E501
+            elif self._firmware == Firmware.STAX:
+                cert_apdu = "01010102010211040000000212010013020002140101160400000000200B45524332305F546F6B656E300200063101083201213321024CCA8FAD496AA5040A00A7EB2F5CC3B85376D88BA147A7D7054A99C6405618873401013501041546304402206731FCD3E2432C5CA162381392FD17AD3A41EEF852E1D706F21A656AB165263602204B89FAE8DBAF191E2D79FB00EBA80D613CB7EDF0BE960CB6F6B29D96E1437F5F"  # noqa: E501
+            elif self._firmware == Firmware.FLEX:
+                cert_apdu = "01010102010211040000000212010013020002140101160400000000200B45524332305F546F6B656E300200063101083201213321024CCA8FAD496AA5040A00A7EB2F5CC3B85376D88BA147A7D7054A99C64056188734010135010515473045022100B59EA8B958AA40578A6FBE9BBFB761020ACD5DBD8AA863C11DA17F42B2AFDE790220186316059EFA58811337D47C7F815F772EA42BBBCEA4AE123D1118C80588F5CB"  # noqa: E501
+            # pylint: enable=line-too-long
+
+            self._pki_client.send_certificate(PKIPubKeyUsage.PUBKEY_USAGE_COIN_META, bytes.fromhex(cert_apdu))
+
+        # Add the network info
+        payload = self._prepare_network_info(name, ticker, chain_id, icon)
+        chunks = self._cmd_builder.provide_network_information(payload, icon)
+        for chunk in chunks[:-1]:
+            response = self._exchange(chunk)
+            assert response.status == StatusWord.OK
+        response = self._exchange(chunks[-1])
+        assert response.status == StatusWord.OK
diff --git a/client/src/ledger_app_clients/ethereum/command_builder.py b/client/src/ledger_app_clients/ethereum/command_builder.py
index ee59fb12e..c43fe9462 100644
--- a/client/src/ledger_app_clients/ethereum/command_builder.py
+++ b/client/src/ledger_app_clients/ethereum/command_builder.py
@@ -3,7 +3,7 @@
 
 import struct
 from enum import IntEnum
-from typing import Optional
+from typing import List, Optional
 from ragger.bip import pack_derivation_path
 
 from .eip712 import EIP712FieldType
@@ -25,6 +25,7 @@ class InsType(IntEnum):
     GET_CHALLENGE = 0x20
     PROVIDE_TRUSTED_NAME = 0x22
     EXTERNAL_PLUGIN_SETUP = 0x12
+    PROVIDE_NETWORK_INFORMATION = 0x30
 
 
 class P1Type(IntEnum):
@@ -32,6 +33,8 @@ class P1Type(IntEnum):
     PARTIAL_SEND = 0x01
     SIGN_FIRST_CHUNK = 0x00
     SIGN_SUBSQT_CHUNK = 0x80
+    FIRST_CHUNK = 0x01
+    FOLLOWING_CHUNK = 0x00
 
 
 class P2Type(IntEnum):
@@ -48,6 +51,8 @@ class P2Type(IntEnum):
     FILTERING_TOKEN_ADDR_CHECK = 0xfd
     FILTERING_AMOUNT_FIELD = 0xfe
     FILTERING_RAW = 0xff
+    NETWORK_CONFIG = 0x00
+    NETWORK_ICON = 0x01
 
 
 class CommandBuilder:
@@ -262,21 +267,11 @@ def sign(self, bip32_path: str, rlp_data: bytes, vrs: list) -> list[bytes]:
         payload += rlp_data
         p1 = P1Type.SIGN_FIRST_CHUNK
         while len(payload) > 0:
-            chunk_size = 0xff
-
-            # TODO: Fix the app & remove this, issue #409
-            if len(vrs) == 3:
-                if len(payload) > chunk_size:
-                    import rlp
-                    diff = len(rlp.encode(vrs)) - (len(payload) - chunk_size)
-                    if diff > 0:
-                        chunk_size -= diff
-
             apdus.append(self._serialize(InsType.SIGN,
                                          p1,
                                          0x00,
-                                         payload[:chunk_size]))
-            payload = payload[chunk_size:]
+                                         payload[:0xff]))
+            payload = payload[0xff:]
             p1 = P1Type.SIGN_SUBSQT_CHUNK
         return apdus
 
@@ -408,3 +403,27 @@ def provide_erc20_token_information(self,
                                0x00,
                                0x00,
                                payload)
+
+    def provide_network_information(self,
+                                    tlv_payload: bytes,
+                                    icon: Optional[bytes] = None) -> list[bytes]:
+        chunks: List[bytes] = []
+
+        # Check if the TLV payload is larger than 0xff
+        assert len(tlv_payload) < 0xff, "Payload too large"
+        # Serialize the payload
+        chunks.append(self._serialize(InsType.PROVIDE_NETWORK_INFORMATION,
+                                      0x00,
+                                      P2Type.NETWORK_CONFIG,
+                                      tlv_payload))
+
+        if icon:
+            p1 = P1Type.FIRST_CHUNK
+            while len(icon) > 0:
+                chunks.append(self._serialize(InsType.PROVIDE_NETWORK_INFORMATION,
+                                              p1,
+                                              P2Type.NETWORK_ICON,
+                                              icon[:0xff]))
+                icon = icon[0xff:]
+                p1 = P1Type.FOLLOWING_CHUNK
+        return chunks
diff --git a/doc/eth_contract_support_embedded.adoc b/doc/eth_contract_support_embedded.adoc
index 629f994cc..1195e8d8d 100644
--- a/doc/eth_contract_support_embedded.adoc
+++ b/doc/eth_contract_support_embedded.adoc
@@ -1,10 +1,6 @@
-Ethereum application : Embedded Contract Support
+= Ethereum application : Embedded Contract Support
 ================================================
-Ledger Firmware Team <hello@ledger.fr>
-Application version 1.3.0 - 05th of July 2020
 
-## 1.3.0
-  - Initial release
 
 ## About
 
diff --git a/doc/ethapp.adoc b/doc/ethapp.adoc
index 90bdef47d..a8358a42e 100644
--- a/doc/ethapp.adoc
+++ b/doc/ethapp.adoc
@@ -1,53 +1,6 @@
-Ethereum application : Common Technical Specifications
+= Ethereum application : Common Technical Specifications
 =======================================================
-Ledger Firmware Team <hello@ledger.fr>
-Application version 1.9.19 - 2022-05-17
-
-## Version history
-
-### 1.0
-  - Initial release
-
-### 1.1
-  - Add GET APP CONFIGURATION
-  - Add an option to return the chain code in GET ETH PUBLIC ADDRESS
-
-### 1.2
-  - Add SIGN ETH PERSONAL MESSAGE
-
-### 1.1.10
-  - Add PROVIDE ERC 20 TOKEN INFORMATION
-
-### 1.5.0
-  - Add SIGN ETH EIP 712
-  - Add GET ETH2 PUBLIC KEY
-
-### 1.7.6
-  - Add SET EXTERNAL PLUGIN
-
-### 1.9.13
-  - Add SET PLUGIN
-
-### 1.9.17
-  - Add PROVIDE NFT INFORMATION
-
-### 1.9.18
-  - Add PERFORM PRIVACY OPERATION
-
-### 1.10.0
-  - Add EIP712 STRUCT DEFINITION & EIP712 STRUCT IMPLEMENTATION
-  - Update to SIGN ETH EIP712
-
-### 1.10.2
-  - Add trusted names support
-
-### 1.11.0
-  - Add EIP-712 amount-join & date/time filtering
-  - PROVIDE ERC 20 TOKEN INFORMATION & PROVIDE NFT INFORMATION now send back the index where the asset has been stored
-
-### 1.12.0
-  - Add EIP-712 discarded filter path support
-  - Add EIP-712 trusted name filtering
+:toc:
 
 ## About
 
@@ -61,6 +14,34 @@ The application covers the following functionalities :
 
 The application interface can be accessed over HID or BLE
 
+## APDUs list
+
+The following APDUs are managed by the application. They are detailed in the following chapters.
+
+[width="80%"]
+|==============================================================================================================================
+| *Description*                                                                     | *Instruction*
+| GET ETH PUBLIC ADDRESS                                                            | 0x02
+| SIGN ETH TRANSACTION                                                              | 0x04
+| GET APP CONFIGURATION                                                             | 0x06
+| SIGN ETH PERSONAL MESSAGE                                                         | 0x08
+| PROVIDE ERC 20 TOKEN INFORMATION                                                  | 0x0A
+| SIGN ETH EIP 712                                                                  | 0x0C
+| GET ETH2 PUBLIC KEY                                                               | 0x0E
+| SET ETH2 WITHDRAWAL INDEX                                                         | 0x10
+| SET EXTERNAL PLUGIN                                                               | 0x12
+| PROVIDE NFT INFORMATION                                                           | 0x14
+| SET PLUGIN                                                                        | 0x16
+| PERFORM PRIVACY OPERATION                                                         | 0x18
+| EIP712 SEND STRUCT DEFINITION                                                     | 0x1A
+| EIP712 SEND STRUCT IMPLEMENTATION                                                 | 0x1C
+| EIP712 FILTERING                                                                  | 0x1E
+| GET CHALLENGE                                                                     | 0x20
+| PROVIDE DOMAIN NAME                                                               | 0x22
+| PROVIDE NETWORK INFORMATION                                                       | 0x30
+|==============================================================================================================================
+
+
 ## General purpose APDUs
 
 ### GET ETH PUBLIC ADDRESS
@@ -1089,6 +1070,92 @@ _Output data_
 None
 
 
+### PROVIDE NETWORK CONFIGURATION
+
+#### Description
+
+This command handles the dynamic networks configuration, allowing to access funds
+without needing to update the application for each new network.
+
+This configuration must be send before any access to a network and stays valid until a new config is sent.
+Up to *2* different configurations can be used. The targeted slot is configured automatically to the next available one.
+
+The configuration is sent in TLV (Tag-Length-Value) mode, whereas the icon itself is send as raw bytes in dedicated chunk(s).
+The configuration doesn't include the icon itself, but only its hash (`sha256`), and it is signed.
+
+For the configuration, the following tags are supported:
+
+[width="80%"]
+|===============================================================
+| *Tag name*        | *Tag value* | *Length (byte)*   | *Value*
+| STRUCTURE_TYPE    | 0x01        | 0x01              | 0x08 (for TYPE_DYNAMIC_NETWORK)
+| STRUCTURE_VERSION | 0x02        | 0x01              | 0x01
+| BLOCKCHAIN_FAMILY | 0x51        | 0x01              | 0x01 (for Ethereum)
+| CHAIN_ID          | 0x23        | 0x08              | (`uint64_t` value)
+| NETWORK_NAME      | 0x52        | variable (max 31) | string without '\0'
+| NETWORK_TICKER    | 0x24        | variable (max 10) | string without '\0'
+| NETWORK_ICON_HASH | 0x53        | 0x20              | _sha256_ of the network icon, sent separately
+| SIGNATURE         | 0x15        | variable          | Signature of the TLV payload data
+|===============================================================
+
+The signature is mandatory, and is computed on the full payload data, using `CX_CURVE_SECP256K1`.
+
+#### Coding
+
+_Command_
+
+[width="80%"]
+|==============================================================
+| *CLA*     | *INS*    | *P1*             | *P2*                       | *LC*     | *Le*
+.3+| E0  .3+| 30       | 00               | 00 : Network configuration | variable | variable
+                       | 01 : First chunk
+
+                         00: Next chunks  | 01 : Network Icon          | variable | variable
+                       | 00               | 02 : Get Info              |          |
+|==============================================================
+
+_Input data_
+
+##### If P1 == Network configuration
+
+[width="80%"]
+|==========================================
+| *Description*         | *Length (byte)*
+| TLV payload data      | variable
+|==========================================
+
+##### If P1 == Network Icon
+
+[width="80%"]
+|==========================================
+| *Description*         | *Length (byte)*
+| Icon data             | variable
+|==========================================
+
+> Notes:
+>
+>  - No need to specify the full size because it is already included in the data header
+>
+>  - The icon is not supported on Nano devices and the corresponding APDU will be ignored.
+>
+>  - Depending on its size, it may be split on several chunks. The maximum allowed bitmap size is `1KB`.
+>
+>  - The data correspond the hex string generated by the script `<SDK_PATH>/lib_nbgl/tools/icon2glyph.py`, with parameter `--hexbitmap`
+
+_Output data_
+
+##### If P1 == Get Info
+
+[width="80%"]
+|==========================================
+| *Description*         | *Length (byte)*
+| Number of networks    | 1
+| Networks chain_id     | 8
+| ...                   | 8
+| Networks chain_id     | 8
+|==========================================
+
+
 ## Transport protocol
 
 ### General transport description
@@ -1172,23 +1239,23 @@ The following standard Status Words are returned for all APDUs - some specific S
 
 [width="80%"]
 |===============================================================================================
-| *SW*     | *Description*
-|   6001   | Mode check fail
-|   6501   | TransactionType not supported
-|   6502   | Output buffer too small for chainId conversion
-|   68xx   | Internal error (Please report)
-|   6982   | Security status not satisfied (Canceled by user)
-|   6983   | Wrong Data length
-|   6984   | Plugin not installed
-|   6985   | Condition not satisfied
-|   6A00   | Error without info
-|   6A80   | Invalid data
-|   6A84   | Insufficient memory
-|   6A88   | Data not found
-|   6B00   | Incorrect parameter P1 or P2
-|   6D00   | Incorrect parameter INS
-|   6E00   | Incorrect parameter CLA
-|   6Fxx   | Technical problem (Internal error, please report)
-|   9000   | Normal ending of the command
-|   911C   | Command code not supported (i.e. Ledger-PKI not yet available)
+| *SW*      | *Description*
+|   0x6001  | Mode check fail
+|   0x6501  | TransactionType not supported
+|   0x6502  | Output buffer too small for chainId conversion
+|   0x68xx  | Internal error (Please report)
+|   0x6982  | Security status not satisfied (Canceled by user)
+|   0x6983  | Wrong Data length
+|   0x6984  | Plugin not installed
+|   0x6985  | Condition not satisfied
+|   0x6A00  | Error without info
+|   0x6A80  | Invalid data
+|   0x6A84  | Insufficient memory
+|   0x6A88  | Data not found
+|   0x6B00  | Incorrect parameter P1 or P2
+|   0x6D00  | Incorrect parameter INS
+|   0x6E00  | Incorrect parameter CLA
+|   0x6Fxx  | Technical problem (Internal error, please report)
+|   0x9000  | Normal ending of the command
+|   0x911C  | Command code not supported (i.e. Ledger-PKI not yet available)
 |===============================================================================================
diff --git a/doc/ethapp_plugins.adoc b/doc/ethapp_plugins.adoc
index 3e2bcbdae..ebdd05630 100644
--- a/doc/ethapp_plugins.adoc
+++ b/doc/ethapp_plugins.adoc
@@ -1,12 +1,7 @@
-Ethereum application Plugins : Technical Specifications
+= Ethereum application Plugins : Technical Specifications
 =======================================================
-Ledger Firmware Team <hello@ledger.fr>
-Specification version 1.0 - 24th of September 2020
 
 
-## 1.0
-  - Initial release
-
 ## About
 
 This specification describes the plugin interface used to display a specific UI on device for Ethereum smart contracts.
diff --git a/glyphs/chain_138_64px.gif b/glyphs/chain_138_64px.gif
new file mode 100644
index 000000000..e16a0f4f9
Binary files /dev/null and b/glyphs/chain_138_64px.gif differ
diff --git a/glyphs/chain_200810_64px.gif b/glyphs/chain_200810_64px.gif
new file mode 120000
index 000000000..3fd9ffe6f
--- /dev/null
+++ b/glyphs/chain_200810_64px.gif
@@ -0,0 +1,1 @@
+chain_200901_64px.gif
\ No newline at end of file
diff --git a/glyphs/chain_200901_64px.gif b/glyphs/chain_200901_64px.gif
new file mode 100644
index 000000000..ba4aecb28
Binary files /dev/null and b/glyphs/chain_200901_64px.gif differ
diff --git a/glyphs/chain_4689_64px.gif b/glyphs/chain_4689_64px.gif
new file mode 100644
index 000000000..07830f267
Binary files /dev/null and b/glyphs/chain_4689_64px.gif differ
diff --git a/glyphs/chain_4690_64px.gif b/glyphs/chain_4690_64px.gif
new file mode 120000
index 000000000..3b49ef1bd
--- /dev/null
+++ b/glyphs/chain_4690_64px.gif
@@ -0,0 +1,1 @@
+chain_4689_64px.gif
\ No newline at end of file
diff --git a/glyphs/chain_47763_64px.gif b/glyphs/chain_47763_64px.gif
new file mode 100644
index 000000000..9c4482918
Binary files /dev/null and b/glyphs/chain_47763_64px.gif differ
diff --git a/icons/flex_app_chain_4689.gif b/icons/flex_app_chain_4689.gif
new file mode 100644
index 000000000..807bcca0b
Binary files /dev/null and b/icons/flex_app_chain_4689.gif differ
diff --git a/icons/nanos_app_chain_4689.gif b/icons/nanos_app_chain_4689.gif
new file mode 100644
index 000000000..4eb701b5c
Binary files /dev/null and b/icons/nanos_app_chain_4689.gif differ
diff --git a/icons/nanox_app_chain_4689.gif b/icons/nanox_app_chain_4689.gif
new file mode 100644
index 000000000..803797eed
Binary files /dev/null and b/icons/nanox_app_chain_4689.gif differ
diff --git a/icons/stax_app_chain_4689.gif b/icons/stax_app_chain_4689.gif
new file mode 100644
index 000000000..de40f2c07
Binary files /dev/null and b/icons/stax_app_chain_4689.gif differ
diff --git a/makefile_conf/chain/iotex.mk b/makefile_conf/chain/iotex.mk
new file mode 100644
index 000000000..f69d79e7c
--- /dev/null
+++ b/makefile_conf/chain/iotex.mk
@@ -0,0 +1,4 @@
+PATH_APP_LOAD_PARAMS += "44'/304'"
+TICKER = "IOTX"
+CHAIN_ID = 4689
+APPNAME = "IoTeX"
\ No newline at end of file
diff --git a/src/apdu_constants.h b/src/apdu_constants.h
index f322c89c3..d523e8184 100644
--- a/src/apdu_constants.h
+++ b/src/apdu_constants.h
@@ -25,12 +25,15 @@
 #define INS_EIP712_FILTERING                0x1E
 #define INS_ENS_GET_CHALLENGE               0x20
 #define INS_ENS_PROVIDE_INFO                0x22
+#define INS_PROVIDE_NETWORK_CONFIGURATION   0x30
 #define P1_CONFIRM                          0x01
 #define P1_NON_CONFIRM                      0x00
 #define P2_NO_CHAINCODE                     0x00
 #define P2_CHAINCODE                        0x01
 #define P1_FIRST                            0x00
 #define P1_MORE                             0x80
+#define P1_FIRST_CHUNK                      0x01
+#define P1_FOLLOWING_CHUNK                  0x00
 #define P2_EIP712_LEGACY_IMPLEM             0x00
 #define P2_EIP712_FULL_IMPLEM               0x01
 
diff --git a/src/chainConfig.h b/src/chainConfig.h
index 9c5490757..cc0f5b8d5 100644
--- a/src/chainConfig.h
+++ b/src/chainConfig.h
@@ -21,9 +21,10 @@
 #include <stdint.h>
 
 #include "os.h"
+#include "asset_info.h"
 
 typedef struct chain_config_s {
-    char coinName[10];  // ticker
+    char coinName[MAX_TICKER_LEN];  // ticker
     uint64_t chainId;
 } chain_config_t;
 
diff --git a/src/ethUstream.c b/src/ethUstream.c
index 6616efff7..eebf8e993 100644
--- a/src/ethUstream.c
+++ b/src/ethUstream.c
@@ -530,19 +530,6 @@ static parserStatus_e processTxInternal(txContext_t *context) {
             PRINTF("parsing is done\n");
             return USTREAM_FINISHED;
         }
-        // Old style transaction (pre EIP-155). Transactions could just skip `v,r,s` so we needed to
-        // cut parsing here. commandLength == 0 could happen in two cases :
-        // 1. We are in an old style transaction : just return `USTREAM_FINISHED`.
-        // 2. We are at the end of an APDU in a multi-apdu process. This would make us return
-        // `USTREAM_FINISHED` preemptively. Case number 2 should NOT happen as it is up to
-        // `ledgerjs` to correctly decrease the size of the APDU (`commandLength`) so that this
-        // situation doesn't happen.
-        if ((context->txType == LEGACY && context->currentField == LEGACY_RLP_V) &&
-            (context->commandLength == 0)) {
-            context->content->vLength = 0;
-            PRINTF("finished\n");
-            return USTREAM_FINISHED;
-        }
         if (context->commandLength == 0) {
             PRINTF("Command length done\n");
             return USTREAM_PROCESSING;
diff --git a/src/ledger_pki.c b/src/ledger_pki.c
index 65d2bb38d..214bb42aa 100644
--- a/src/ledger_pki.c
+++ b/src/ledger_pki.c
@@ -1,8 +1,5 @@
 #include "apdu_constants.h"
 #include "public_keys.h"
-#ifdef HAVE_LEDGER_PKI
-#include "os_pki.h"
-#endif
 
 #define KEY_USAGE_STR(x)                                                               \
     (x == CERTIFICATE_PUBLIC_KEY_USAGE_GENUINE_CHECK          ? "GENUINE_CHECK"        \
diff --git a/src/main.c b/src/main.c
index 0a6793f65..c0ebeae74 100644
--- a/src/main.c
+++ b/src/main.c
@@ -35,6 +35,7 @@
 #include "trusted_name.h"
 #include "crypto_helpers.h"
 #include "manage_asset_info.h"
+#include "network_dynamic.h"
 
 tmpCtx_t tmpCtx;
 txContext_t txContext;
@@ -135,6 +136,9 @@ static uint16_t handleApdu(command_t *cmd, uint32_t *flags, uint32_t *tx) {
     }
 
     switch (cmd->ins) {
+        case INS_PROVIDE_NETWORK_CONFIGURATION:
+            sw = handleNetworkConfiguration(cmd->p1, cmd->p2, cmd->data, cmd->lc, tx);
+            break;
         case INS_GET_PUBLIC_KEY:
             forget_known_assets();
             sw = handleGetPublicKey(cmd->p1, cmd->p2, cmd->data, cmd->lc, flags, tx);
diff --git a/src/mem.c b/src/mem.c
index b0b993a7f..8de2e52ea 100644
--- a/src/mem.c
+++ b/src/mem.c
@@ -10,6 +10,7 @@
 
 #include <stdint.h>
 #include "mem.h"
+#include "os_print.h"
 
 #define SIZE_MEM_BUFFER 10240
 
@@ -42,6 +43,7 @@ void mem_reset(void) {
 void *mem_alloc(size_t size) {
     if ((mem_idx + size) > SIZE_MEM_BUFFER)  // Buffer exceeded
     {
+        PRINTF("Error: mem_alloc(%u) failed!\n", size);
         return NULL;
     }
     mem_idx += size;
diff --git a/src/network.c b/src/network.c
index d9ccd6617..521e6d057 100644
--- a/src/network.c
+++ b/src/network.c
@@ -2,16 +2,11 @@
 #include "os_utils.h"
 #include "os_pic.h"
 #include "network.h"
+#include "network_dynamic.h"
 #include "shared_context.h"
 #include "common_utils.h"
 
-typedef struct network_info_s {
-    const char *name;
-    const char *ticker;
-    uint64_t chain_id;
-} network_info_t;
-
-static const char *unknown_ticker = "???";
+const char g_unknown_ticker[] = "???";
 
 // Mapping of chain ids to networks.
 static const network_info_t NETWORK_MAPPING[] = {
@@ -39,6 +34,7 @@ static const network_info_t NETWORK_MAPPING[] = {
     {.chain_id = 100, .name = "Gnosis", .ticker = "xDAI"},
     {.chain_id = 106, .name = "Velas EVM", .ticker = "VLX"},
     {.chain_id = 137, .name = "Polygon", .ticker = "POL"},
+    {.chain_id = 138, .name = "Defi Oracle Meta", .ticker = "ETH"},
     {.chain_id = 196, .name = "OKBChain Mainnet", .ticker = "OKB"},
     {.chain_id = 199, .name = "BTTC", .ticker = "BTT"},
     {.chain_id = 246, .name = "EnergyWebChain", .ticker = "EWT"},
@@ -66,6 +62,8 @@ static const network_info_t NETWORK_MAPPING[] = {
     {.chain_id = 3776, .name = "Astar zkEVM", .ticker = "ETH"},
     {.chain_id = 4201, .name = "LUKSO Testnet", .ticker = "LYXt"},
     {.chain_id = 4202, .name = "Lisk Sepolia Testnet", .ticker = "ETH"},
+    {.chain_id = 4689, .name = "IoTeX", .ticker = "IOTX"},
+    {.chain_id = 4690, .name = "IoTeX Testnet", .ticker = "IOTX"},
     {.chain_id = 4919, .name = "Venidium", .ticker = "XVM"},
     {.chain_id = 5000, .name = "Mantle", .ticker = "MNT"},
     {.chain_id = 5003, .name = "Mantle Sepolia", .ticker = "MNT"},
@@ -84,6 +82,7 @@ static const network_info_t NETWORK_MAPPING[] = {
     {.chain_id = 42793, .name = "Etherlink Mainnet", .ticker = "XTZ"},
     {.chain_id = 43114, .name = "Avalanche", .ticker = "AVAX"},
     {.chain_id = 44787, .name = "Celo Alfajores", .ticker = "aCELO"},
+    {.chain_id = 47763, .name = "Neo X Mainnet", .ticker = "GAS"},
     {.chain_id = 52014, .name = "Electroneum", .ticker = "ETN"},
     {.chain_id = 59141, .name = "Linea Sepolia", .ticker = "ETH"},
     {.chain_id = 59144, .name = "Linea", .ticker = "ETH"},
@@ -93,6 +92,8 @@ static const network_info_t NETWORK_MAPPING[] = {
     {.chain_id = 73799, .name = "Volta", .ticker = "VOLTA"},
     {.chain_id = 81457, .name = "Blast", .ticker = "ETH"},
     {.chain_id = 84532, .name = "Base Sepolia", .ticker = "ETH"},
+    {.chain_id = 200810, .name = "Bitlayer Testnet", .ticker = "BTC"},
+    {.chain_id = 200901, .name = "Bitlayer", .ticker = "BTC"},
     {.chain_id = 421614, .name = "Arbitrum Sepolia", .ticker = "ETH"},
     {.chain_id = 534351, .name = "Scroll Sepolia", .ticker = "ETH"},
     {.chain_id = 534352, .name = "Scroll", .ticker = "ETH"},
@@ -100,6 +101,7 @@ static const network_info_t NETWORK_MAPPING[] = {
     {.chain_id = 5201420, .name = "Electroneum Testnet", .ticker = "ETN"},
     {.chain_id = 11155111, .name = "Sepolia", .ticker = "ETH"},
     {.chain_id = 11155420, .name = "OP Sepolia", .ticker = "ETH"},
+    {.chain_id = 12227332, .name = "Neo X Testnet", .ticker = "GAS"},
     {.chain_id = 20531811, .name = "TecraTestnet", .ticker = "TCR"},
     {.chain_id = 20531812, .name = "Tecra", .ticker = "TCR"},
     {.chain_id = 168587773, .name = "Blast Sepolia", .ticker = "ETH"},
@@ -109,9 +111,21 @@ static const network_info_t NETWORK_MAPPING[] = {
 };
 
 static const network_info_t *get_network_from_chain_id(const uint64_t *chain_id) {
-    for (size_t i = 0; i < ARRAYLEN(NETWORK_MAPPING); i++) {
-        if (NETWORK_MAPPING[i].chain_id == *chain_id) {
-            return (const network_info_t *) &NETWORK_MAPPING[i];
+    if (*chain_id != 0) {
+        // Look if the network is available
+        for (size_t i = 0; i < MAX_DYNAMIC_NETWORKS; i++) {
+            if (DYNAMIC_NETWORK_INFO[i].chain_id == *chain_id) {
+                PRINTF("[NETWORK] - Found dynamic %s\n", DYNAMIC_NETWORK_INFO[i].name);
+                return (const network_info_t *) &DYNAMIC_NETWORK_INFO[i];
+            }
+        }
+        // Fallback to hardcoded table
+        for (size_t i = 0; i < ARRAYLEN(NETWORK_MAPPING); i++) {
+            if (NETWORK_MAPPING[i].chain_id == *chain_id) {
+                PRINTF("[NETWORK] - Fallback on hardcoded list. Found %s\n",
+                       NETWORK_MAPPING[i].name);
+                return (const network_info_t *) &NETWORK_MAPPING[i];
+            }
         }
     }
     return NULL;
@@ -166,7 +180,7 @@ const char *get_displayable_ticker(const uint64_t *chain_id, const chain_config_
         if (*chain_id == chain_cfg->chainId) {
             ticker = chain_cfg->coinName;
         } else {
-            ticker = unknown_ticker;
+            ticker = g_unknown_ticker;
         }
     }
     return ticker;
diff --git a/src/network.h b/src/network.h
index ee96347b5..6310074bc 100644
--- a/src/network.h
+++ b/src/network.h
@@ -3,12 +3,29 @@
 #include <stdint.h>
 #include <stdbool.h>
 #include "chainConfig.h"
+#ifdef HAVE_NBGL
+#include "nbgl_types.h"
+#endif
+
+#define MAX_NETWORK_LEN 32  // 31 characters + '\0'
+
+typedef struct network_info_s {
+    const char name[MAX_NETWORK_LEN];
+    const char ticker[MAX_TICKER_LEN];
+    uint64_t chain_id;
+#ifdef HAVE_NBGL
+    nbgl_icon_details_t icon;
+#endif
+} network_info_t;
 
 #define UNSUPPORTED_CHAIN_ID_MSG(id)                                              \
     do {                                                                          \
         PRINTF("Unsupported chain ID: %u (app: %u)\n", id, chainConfig->chainId); \
     } while (0)
 
+extern network_info_t DYNAMIC_NETWORK_INFO[];
+extern const char g_unknown_ticker[];
+
 const char *get_network_name_from_chain_id(const uint64_t *chain_id);
 const char *get_network_ticker_from_chain_id(const uint64_t *chain_id);
 
diff --git a/src/public_keys.h b/src/public_keys.h
index 250d7dfa1..d941bbaf2 100644
--- a/src/public_keys.h
+++ b/src/public_keys.h
@@ -16,7 +16,11 @@
  ********************************************************************************/
 
 #pragma once
+
 #include <stdint.h>
+#ifdef HAVE_LEDGER_PKI
+#include "os_pki.h"
+#endif
 
 static const uint8_t LEDGER_SIGNATURE_PUBLIC_KEY[] = {
 #if defined(HAVE_CAL_TEST_KEY)
@@ -105,13 +109,13 @@ static const uint8_t LEDGER_NFT_SELECTOR_PUBLIC_KEY[] = {
 #endif
 };
 
-extern int check_signature_with_pubkey(const char *tag,
-                                       uint8_t *buffer,
-                                       const uint8_t bufLen,
-                                       const uint8_t *PubKey,
-                                       const uint8_t keyLen,
+int check_signature_with_pubkey(const char *tag,
+                                uint8_t *buffer,
+                                const uint8_t bufLen,
+                                const uint8_t *PubKey,
+                                const uint8_t keyLen,
 #ifdef HAVE_LEDGER_PKI
-                                       const uint8_t keyUsageExp,
+                                const uint8_t keyUsageExp,
 #endif
-                                       uint8_t *signature,
-                                       const uint8_t sigLen);
+                                uint8_t *signature,
+                                const uint8_t sigLen);
diff --git a/src/shared_context.h b/src/shared_context.h
index d0fed95d7..eca0fed11 100644
--- a/src/shared_context.h
+++ b/src/shared_context.h
@@ -127,7 +127,7 @@ typedef enum {
 typedef struct txStringProperties_s {
     char fromAddress[43];
     char toAddress[43];
-    char fullAmount[79];  // 2^256 is 78 digits long
+    char fullAmount[MAX_TICKER_LEN + 1 + 78 + 1];  // 2^256 is 78 digits long
     char maxFee[50];
     char nonce[8];  // 10M tx per account ought to be enough for everybody
     char network_name[NETWORK_STRING_MAX_SIZE + 1];
diff --git a/src_features/provideDynamicNetwork/network_dynamic.c b/src_features/provideDynamicNetwork/network_dynamic.c
new file mode 100644
index 000000000..3ff7d7c09
--- /dev/null
+++ b/src_features/provideDynamicNetwork/network_dynamic.c
@@ -0,0 +1,649 @@
+#include <string.h>
+#include <ctype.h>
+#include "os_utils.h"
+#include "os_pic.h"
+#include "network.h"
+#include "network_dynamic.h"
+#include "shared_context.h"
+#include "common_utils.h"
+#include "apdu_constants.h"
+#include "write.h"
+#include "hash_bytes.h"
+#include "lcx_hash.h"
+#include "lcx_sha256.h"
+#include "public_keys.h"
+#ifdef HAVE_LEDGER_PKI
+#include "os_pki.h"
+#endif
+
+#define P2_NETWORK_CONFIG 0x00
+#define P2_NETWORK_ICON   0x01
+#define P2_GET_INFO       0x02
+
+#define MAX_ICON_LEN 1024
+
+#define TYPE_DYNAMIC_NETWORK   0x08
+#define NETWORK_STRUCT_VERSION 0x01
+
+#define BLOCKCHAIN_FAMILY_ETHEREUM 0x01
+
+// Tags are defined here:
+// https://ledgerhq.atlassian.net/wiki/spaces/FW/pages/5039292480/Dynamic+Networks
+enum {
+    TAG_STRUCTURE_TYPE = 0x01,
+    TAG_STRUCTURE_VERSION = 0x02,
+    TAG_BLOCKCHAIN_FAMILY = 0x51,
+    TAG_CHAIN_ID = 0x23,
+    TAG_NETWORK_NAME = 0x52,
+    TAG_TICKER = 0x24,
+    TAG_NETWORK_ICON_HASH = 0x53,
+    TAG_DER_SIGNATURE = 0x15,
+};
+
+// Signature context structure
+typedef struct {
+    uint8_t sig_size;
+    const uint8_t *sig;
+    cx_sha256_t hash_ctx;
+} s_sig_ctx;
+
+// Global variable to store the current slot
+static uint8_t g_current_slot = 0;
+
+#ifdef HAVE_NBGL
+typedef struct {
+    uint16_t received_size;
+    uint16_t expected_size;
+} network_payload_t;
+
+// Global variable to store the network icons
+typedef struct {
+    uint8_t bitmap[MAX_ICON_LEN];
+    uint8_t hash[CX_SHA256_SIZE];
+} network_icon_t;
+// Global structure to store the network icons
+static network_icon_t g_network_icon[MAX_DYNAMIC_NETWORKS] = {0};
+// Global structure to temporary store the network icon APDU
+static network_payload_t g_icon_payload = {0};
+#endif
+
+// Global structure to store the dynamic network information
+network_info_t DYNAMIC_NETWORK_INFO[MAX_DYNAMIC_NETWORKS] = {0};
+
+// Macros to check the field length
+#define CHECK_FIELD_LENGTH(tag, len, expected)  \
+    do {                                        \
+        if (len != expected) {                  \
+            PRINTF("%s Size mismatch!\n", tag); \
+            return APDU_RESPONSE_INVALID_DATA;  \
+        }                                       \
+    } while (0)
+#define CHECK_FIELD_OVERFLOW(tag, field)              \
+    do {                                              \
+        if (field_len >= sizeof(field)) {             \
+            PRINTF("%s Size overflow!\n", tag);       \
+            return APDU_RESPONSE_INSUFFICIENT_MEMORY; \
+        }                                             \
+    } while (0)
+
+// Macro to check the field value
+#define CHECK_FIELD_VALUE(tag, value, expected)  \
+    do {                                         \
+        if (value != expected) {                 \
+            PRINTF("%s Value mismatch!\n", tag); \
+            return APDU_RESPONSE_INVALID_DATA;   \
+        }                                        \
+    } while (0)
+
+// Macro to copy the field
+#define COPY_FIELD(field)                         \
+    do {                                          \
+        memmove((void *) field, data, field_len); \
+    } while (0)
+
+/**
+ * @brief Parse the STRUCTURE_TYPE value.
+ *
+ * @param[in] data buffer received
+ * @param[in] field_len Length of the field value
+ * @return APDU Response code
+ */
+static uint16_t parse_struct_type(const uint8_t *data, uint16_t field_len) {
+    CHECK_FIELD_LENGTH("STRUCTURE_TYPE", field_len, 1);
+    CHECK_FIELD_VALUE("STRUCTURE_TYPE", data[0], TYPE_DYNAMIC_NETWORK);
+    return APDU_RESPONSE_OK;
+}
+
+/**
+ * @brief Parse the STRUCTURE_VERSION value.
+ *
+ * @param[in] data buffer received
+ * @param[in] field_len Length of the field value
+ * @return APDU Response code
+ */
+static uint16_t parse_struct_version(const uint8_t *data, uint16_t field_len) {
+    CHECK_FIELD_LENGTH("STRUCTURE_VERSION", field_len, 1);
+    CHECK_FIELD_VALUE("STRUCTURE_VERSION", data[0], NETWORK_STRUCT_VERSION);
+    return APDU_RESPONSE_OK;
+}
+
+/**
+ * @brief Parse the BLOCKCHAIN_FAMILY value.
+ *
+ * @param[in] data buffer received
+ * @param[in] field_len Length of the field value
+ * @return APDU Response code
+ */
+static uint16_t parse_blockchain_family(const uint8_t *data, uint16_t field_len) {
+    CHECK_FIELD_LENGTH("BLOCKCHAIN_FAMILY", field_len, 1);
+    CHECK_FIELD_VALUE("BLOCKCHAIN_FAMILY", data[0], BLOCKCHAIN_FAMILY_ETHEREUM);
+    return APDU_RESPONSE_OK;
+}
+
+/**
+ * @brief Parse the CHAIN_ID value.
+ *
+ * @param[in] data buffer received
+ * @param[in] field_len Length of the field value
+ * @return APDU Response code
+ */
+static uint16_t parse_chain_id(const uint8_t *data, uint16_t field_len) {
+    uint64_t chain_id;
+    uint64_t max_range;
+    uint8_t i;
+
+    CHECK_FIELD_LENGTH("CHAIN_ID", field_len, sizeof(uint64_t));
+    // Check if the chain ID is supported
+    // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2294.md
+    max_range = 0x7FFFFFFFFFFFFFDB;
+    chain_id = u64_from_BE(data, field_len);
+    // Check if the chain_id is supported
+    if ((chain_id > max_range) || (chain_id == 0)) {
+        PRINTF("Unsupported chain ID: %u\n", chain_id);
+        return APDU_RESPONSE_INVALID_DATA;
+    }
+    // Check if the chain_id is already registered
+    for (i = 0; i < MAX_DYNAMIC_NETWORKS; i++) {
+        if (DYNAMIC_NETWORK_INFO[i].chain_id == chain_id) {
+            PRINTF("CHAIN_ID already exist!\n");
+            return APDU_RESPONSE_INVALID_DATA;
+        }
+    }
+
+    DYNAMIC_NETWORK_INFO[g_current_slot].chain_id = chain_id;
+    return APDU_RESPONSE_OK;
+}
+
+/**
+ * @brief Check the name is printable.
+ *
+ * @param[in] data buffer received
+ * @param[in] name Name to check
+ * @param[in] len Length of the name
+ * @return True/False
+ */
+static bool check_name(const uint8_t *name, uint16_t len) {
+    for (uint16_t i = 0; i < len; i++) {
+        if (!isprint(name[i])) {
+            return false;
+        }
+    }
+    return true;
+}
+
+/**
+ * @brief Parse the NETWORK_NAME value.
+ *
+ * @param[in] data buffer received
+ * @param[in] field_len Length of the field value
+ * @return APDU Response code
+ */
+static uint16_t parse_name(const uint8_t *data, uint16_t field_len) {
+    CHECK_FIELD_OVERFLOW("NETWORK_NAME", DYNAMIC_NETWORK_INFO[g_current_slot].name);
+    // Check if the name is printable
+    if (!check_name(data, field_len)) {
+        PRINTF("NETWORK_NAME is not printable!\n");
+        return APDU_RESPONSE_INVALID_DATA;
+    }
+    COPY_FIELD(DYNAMIC_NETWORK_INFO[g_current_slot].name);
+    return APDU_RESPONSE_OK;
+}
+
+/**
+ * @brief Parse the NETWORK_TICKER value.
+ *
+ * @param[in] data buffer received
+ * @param[in] field_len Length of the field value
+ * @return APDU Response code
+ */
+static uint16_t parse_ticker(const uint8_t *data, uint16_t field_len) {
+    CHECK_FIELD_OVERFLOW("NETWORK_TICKER", DYNAMIC_NETWORK_INFO[g_current_slot].ticker);
+    // Check if the ticker is printable
+    if (!check_name(data, field_len)) {
+        PRINTF("NETWORK_TICKER is not printable!\n");
+        return APDU_RESPONSE_INVALID_DATA;
+    }
+    COPY_FIELD(DYNAMIC_NETWORK_INFO[g_current_slot].ticker);
+    return APDU_RESPONSE_OK;
+}
+
+#ifdef HAVE_NBGL
+/**
+ * @brief Parse the NETWORK_ICON_HASH value.
+ *
+ * @param[in] data buffer received
+ * @param[in] field_len Length of the field value
+ * @return APDU Response code
+ */
+static uint16_t parse_icon_hash(const uint8_t *data, uint16_t field_len) {
+    CHECK_FIELD_LENGTH("NETWORK_ICON_HASH", field_len, CX_SHA256_SIZE);
+    COPY_FIELD(g_network_icon[g_current_slot].hash);
+    return APDU_RESPONSE_OK;
+}
+
+/**
+ * @brief Check the NETWORK_ICON header.
+ *
+ * @param[in] data buffer received
+ * @param[in] length Length of the field value
+ * @return APDU Response code
+ */
+static uint16_t check_icon_header(const uint8_t *data, uint16_t length, uint16_t *buffer_size) {
+    // The chunk starts by the Image Header (8 Bytes):
+    //   - Width (2 Bytes)
+    //   - Height (2 Bytes)
+    //   - BPP (1 Byte)
+    //   - Img buffer size (3 Bytes)
+    if (length < 8) {
+        PRINTF("NETWORK_ICON header length mismatch (%d)!\n", length);
+        return APDU_RESPONSE_INVALID_DATA;
+    }
+    *buffer_size = 8 + data[5] + (data[6] << 8) + (data[7] << 16);
+    return APDU_RESPONSE_OK;
+}
+
+/**
+ * @brief Print the registered network icon.
+ *
+ * Only for debug purpose.
+ *
+ */
+static void print_icon_info(void) {
+    PRINTF("****************************************************************************\n");
+    PRINTF("[NETWORK_ICON] - Registered in slot %d: icon %dx%d (BPP %d)\n",
+           g_current_slot,
+           DYNAMIC_NETWORK_INFO[g_current_slot].icon.width,
+           DYNAMIC_NETWORK_INFO[g_current_slot].icon.height,
+           DYNAMIC_NETWORK_INFO[g_current_slot].icon.bpp);
+}
+
+/**
+ * @brief Parse and check the NETWORK_ICON value.
+ *
+ * @return APDU Response code
+ */
+static uint16_t parse_icon_buffer(void) {
+    uint16_t img_len = 0;
+    uint16_t sw = APDU_RESPONSE_UNKNOWN;
+    uint8_t digest[CX_SHA256_SIZE];
+    const uint8_t *data = g_network_icon[g_current_slot].bitmap;
+    const uint16_t field_len = g_icon_payload.received_size;
+
+    // Check the icon header
+    sw = check_icon_header(data, field_len, &img_len);
+    if (sw != APDU_RESPONSE_OK) {
+        return sw;
+    }
+    CHECK_FIELD_LENGTH("NETWORK_ICON", field_len, img_len);
+    CHECK_FIELD_OVERFLOW("NETWORK_ICON", g_network_icon[g_current_slot].bitmap);
+
+    // Check icon hash
+    cx_sha256_hash(data, field_len, digest);
+    if (memcmp(digest, g_network_icon[g_current_slot].hash, CX_SHA256_SIZE) != 0) {
+        PRINTF("NETWORK_ICON hash mismatch!\n");
+        return APDU_RESPONSE_INVALID_DATA;
+    }
+
+    DYNAMIC_NETWORK_INFO[g_current_slot].icon.bitmap =
+        (const uint8_t *) g_network_icon[g_current_slot].bitmap;
+    DYNAMIC_NETWORK_INFO[g_current_slot].icon.width = U2LE(data, 0);
+    DYNAMIC_NETWORK_INFO[g_current_slot].icon.height = U2LE(data, 2);
+    // BPP is stored in the upper 4 bits of the 5th byte
+    DYNAMIC_NETWORK_INFO[g_current_slot].icon.bpp = data[4] >> 4;
+    DYNAMIC_NETWORK_INFO[g_current_slot].icon.isFile = true;
+    COPY_FIELD(DYNAMIC_NETWORK_INFO[g_current_slot].icon.bitmap);
+    print_icon_info();
+    return APDU_RESPONSE_OK;
+}
+
+/**
+ * @brief Init the dynamic network icon with the 1st chunk.
+ *
+ * Analyze the 1st chunk, containing the icon size
+ *
+ * @param[in] data buffer received, skip payload length
+ * @param[in] length of the buffer, reduced by the payload length
+ * @return APDU Response code
+ */
+static uint16_t handle_first_icon_chunk(const uint8_t *data, uint8_t length) {
+    uint16_t img_len = 0;
+    uint16_t sw = APDU_RESPONSE_UNKNOWN;
+
+    // Reset the structures
+    explicit_bzero(&g_icon_payload, sizeof(g_icon_payload));
+    explicit_bzero(g_network_icon[g_current_slot].bitmap, MAX_ICON_LEN);
+
+    // Check the icon header
+    sw = check_icon_header(data, length, &img_len);
+    if (sw != APDU_RESPONSE_OK) {
+        return sw;
+    }
+    if (img_len > MAX_ICON_LEN) {
+        PRINTF("Icon size too large!\n");
+        return APDU_RESPONSE_INSUFFICIENT_MEMORY;
+    }
+    g_icon_payload.expected_size = img_len;
+
+    return APDU_RESPONSE_OK;
+}
+
+/**
+ * @brief Handle icon data chunk.
+ *
+ * @param[in] data buffer received
+ * @param[in] length of the buffer
+ * @return APDU Response code
+ */
+static uint16_t handle_next_icon_chunk(const uint8_t *data, uint8_t length) {
+    if ((g_icon_payload.received_size + length) > g_icon_payload.expected_size) {
+        PRINTF("Payload size mismatch!\n");
+        return APDU_RESPONSE_INVALID_DATA;
+    }
+    // Feed into payload
+    memcpy(g_network_icon[g_current_slot].bitmap + g_icon_payload.received_size, data, length);
+    g_icon_payload.received_size += length;
+
+    return APDU_RESPONSE_OK;
+}
+
+/**
+ * @brief Handle icon chunks.
+ *
+ * @param[in] p1 APDU parameter 1
+ * @param[in] data buffer received
+ * @param[in] length of the buffer
+ * @return APDU Response code
+ */
+static uint16_t handle_icon_chunks(uint8_t p1, const uint8_t *data, uint8_t length) {
+    uint16_t sw = APDU_RESPONSE_UNKNOWN;
+
+    // Check the received chunk index
+    if (p1 == P1_FIRST_CHUNK) {
+        // Init the with the 1st chunk
+        sw = handle_first_icon_chunk(data, length);
+        if (sw != APDU_RESPONSE_OK) {
+            return sw;
+        }
+    } else if (p1 != P1_FOLLOWING_CHUNK) {
+        PRINTF("Error: Unexpected P2 (%u)!\n", p1);
+        return APDU_RESPONSE_INVALID_P1_P2;
+    }
+
+    // Handle the payload
+    sw = handle_next_icon_chunk(data, length);
+    if (sw != APDU_RESPONSE_OK) {
+        return sw;
+    }
+    if (g_icon_payload.received_size == g_icon_payload.expected_size) {
+        // Everything has been received
+        sw = parse_icon_buffer();
+    }
+    return sw;
+}
+#endif
+
+/**
+ * @brief Parse the SIGNATURE value.
+ *
+ * @param[in] data buffer received
+ * @param[in] field_len Length of the field value
+ * @param[in] sig_ctx the signature context
+ * @return APDU Response code
+ */
+static uint16_t parse_signature(const uint8_t *data, uint16_t field_len, s_sig_ctx *sig_ctx) {
+    sig_ctx->sig_size = field_len;
+    sig_ctx->sig = data;
+    return APDU_RESPONSE_OK;
+}
+
+/**
+ * @brief Verify the payload signature
+ *
+ * Verify the SHA-256 hash of the payload against the public key
+ *
+ * @param[in] sig_ctx the signature context
+ * @return whether it was successful
+ */
+static bool verify_signature(s_sig_ctx *sig_ctx) {
+    uint8_t hash[INT256_LENGTH];
+    cx_err_t error = CX_INTERNAL_ERROR;
+    bool ret_code = false;
+
+    CX_CHECK(
+        cx_hash_no_throw((cx_hash_t *) &sig_ctx->hash_ctx, CX_LAST, NULL, 0, hash, INT256_LENGTH));
+
+    CX_CHECK(check_signature_with_pubkey("Dynamic Network",
+                                         hash,
+                                         sizeof(hash),
+                                         LEDGER_SIGNATURE_PUBLIC_KEY,
+                                         sizeof(LEDGER_SIGNATURE_PUBLIC_KEY),
+#ifdef HAVE_LEDGER_PKI
+                                         CERTIFICATE_PUBLIC_KEY_USAGE_COIN_META,
+#endif
+                                         (uint8_t *) (sig_ctx->sig),
+                                         sig_ctx->sig_size));
+
+    ret_code = true;
+end:
+    return ret_code;
+}
+
+/**
+ * @brief Print the registered network.
+ *
+ * Only for debug purpose.
+ */
+static void print_network_info(void) {
+    char chain_str[sizeof(uint64_t) * 2 + 1] = {0};
+
+    PRINTF("****************************************************************************\n");
+    u64_to_string(DYNAMIC_NETWORK_INFO[g_current_slot].chain_id, chain_str, sizeof(chain_str));
+    PRINTF("[NETWORK] - Registered in slot %d: %s (%s), for chain_id %s\n",
+           g_current_slot,
+           DYNAMIC_NETWORK_INFO[g_current_slot].name,
+           DYNAMIC_NETWORK_INFO[g_current_slot].ticker,
+           chain_str);
+}
+
+/**
+ * @brief Parse the TLV payload containing the Network configuration.
+ *
+ * @param[in] data buffer received
+ * @param[in] length of the buffer
+ * @return APDU Response code
+ */
+static uint16_t parse_tlv(const uint8_t *data, uint8_t length) {
+    uint16_t offset = 0;
+    uint16_t field_tag = 0;
+    uint16_t field_len = 0;
+    uint16_t sw = APDU_RESPONSE_INTERNAL_ERROR;
+    uint16_t tag_start_off;
+    s_sig_ctx sig_ctx = {0};
+
+    // Set the current slot here, because the corresponding icon will be received
+    // separately, after the network configuration, and should keep the same slot
+    g_current_slot = (g_current_slot + 1) % MAX_DYNAMIC_NETWORKS;
+
+    // Reset the structures
+    explicit_bzero(&DYNAMIC_NETWORK_INFO[g_current_slot], sizeof(network_info_t));
+#ifdef HAVE_NBGL
+    explicit_bzero(&g_network_icon[g_current_slot], sizeof(network_icon_t));
+#endif
+    // Initialize the hash context
+    cx_sha256_init(&sig_ctx.hash_ctx);
+    // handle TLV payload
+    while (offset != length) {
+        if ((offset + 2) > length) {
+            sw = APDU_RESPONSE_INVALID_DATA;
+            break;
+        }
+        tag_start_off = offset;
+        field_tag = data[offset++];
+        field_len = data[offset++];
+        if ((offset + field_len) > length) {
+            PRINTF("Field length mismatch (%d/%d)!\n", (offset + field_len), length);
+            sw = APDU_RESPONSE_INVALID_DATA;
+            break;
+        }
+        switch (field_tag) {
+            case TAG_STRUCTURE_TYPE:
+                sw = parse_struct_type(data + offset, field_len);
+                break;
+            case TAG_STRUCTURE_VERSION:
+                sw = parse_struct_version(data + offset, field_len);
+                break;
+            case TAG_BLOCKCHAIN_FAMILY:
+                sw = parse_blockchain_family(data + offset, field_len);
+                break;
+            case TAG_CHAIN_ID:
+                sw = parse_chain_id(data + offset, field_len);
+                break;
+            case TAG_NETWORK_NAME:
+                sw = parse_name(data + offset, field_len);
+                break;
+            case TAG_TICKER:
+                sw = parse_ticker(data + offset, field_len);
+                break;
+            case TAG_NETWORK_ICON_HASH:
+#ifdef HAVE_NBGL
+                sw = parse_icon_hash(data + offset, field_len);
+#endif
+                break;
+            case TAG_DER_SIGNATURE:
+                sw = parse_signature(data + offset, field_len, &sig_ctx);
+                break;
+            default:
+                PRINTF("Skipping unknown tag: %d\n", field_tag);
+                sw = APDU_RESPONSE_OK;
+                break;
+        }
+        if (sw != APDU_RESPONSE_OK) {
+            break;
+        }
+        offset += field_len;
+        if (field_tag != TAG_DER_SIGNATURE) {  // the signature wasn't computed on itself
+            hash_nbytes(data + tag_start_off,
+                        (offset - tag_start_off),
+                        (cx_hash_t *) &sig_ctx.hash_ctx);
+        }
+    }
+    if (sw == APDU_RESPONSE_OK) {
+        if (verify_signature(&sig_ctx) == false) {
+            PRINTF("Signature verification failed!\n");
+            sw = APDU_RESPONSE_INVALID_DATA;
+        }
+    }
+    if (sw == APDU_RESPONSE_OK) {
+        print_network_info();
+    } else {
+        // Reset the structure
+        explicit_bzero(&DYNAMIC_NETWORK_INFO[g_current_slot], sizeof(network_info_t));
+    }
+    return sw;
+}
+
+/**
+ * @brief Returns the current network configuration.
+ *
+ * @return APDU length
+ */
+static uint16_t handle_get_config(void) {
+    uint8_t chain_str[sizeof(uint64_t) * 2 + 1];
+    uint32_t tx = 1;  // Init to '1' because there is at least the number of networks
+    uint16_t nb_networks = 0;
+
+    for (size_t i = 0; i < MAX_DYNAMIC_NETWORKS; i++) {
+        if (DYNAMIC_NETWORK_INFO[i].chain_id != 0) {
+            PRINTF("[NETWORK] - Found dynamic %s\n", DYNAMIC_NETWORK_INFO[i].name);
+            // Convert chain_id
+            explicit_bzero(chain_str, sizeof(chain_str));
+            write_u64_be(chain_str, 0, DYNAMIC_NETWORK_INFO[i].chain_id);
+            memmove(G_io_apdu_buffer + tx, chain_str, sizeof(uint64_t));
+            tx += sizeof(uint64_t);
+            nb_networks++;
+        }
+    }
+    G_io_apdu_buffer[0] = nb_networks;
+
+    return tx;
+}
+
+/**
+ * @brief Handle Network Configuration APDU.
+ *
+ * @param[in] p1 APDU parameter 1
+ * @param[in] p2 APDU parameter 2
+ * @param[in] data buffer received
+ * @param[in] length of the buffer
+ * @param[in] tx output length
+ * @return APDU Response code
+ */
+uint16_t handleNetworkConfiguration(uint8_t p1,
+                                    uint8_t p2,
+                                    const uint8_t *data,
+                                    uint8_t length,
+                                    unsigned int *tx) {
+    uint16_t sw = APDU_RESPONSE_UNKNOWN;
+
+    switch (p2) {
+        case P2_NETWORK_CONFIG:
+            if (p1 != 0x00) {
+                PRINTF("Error: Unexpected P1 (%u)!\n", p1);
+                sw = APDU_RESPONSE_INVALID_P1_P2;
+                break;
+            }
+            sw = parse_tlv(data, length);
+            break;
+
+        case P2_NETWORK_ICON:
+#ifdef HAVE_NBGL
+            sw = handle_icon_chunks(p1, data, length);
+#else
+            PRINTF("Warning: Network icon not supported!\n");
+            sw = APDU_RESPONSE_OK;
+#endif
+            break;
+
+        case P2_GET_INFO:
+            if (p1 != 0x00) {
+                PRINTF("Error: Unexpected P1 (%u)!\n", p1);
+                sw = APDU_RESPONSE_INVALID_P1_P2;
+                break;
+            }
+            *tx = handle_get_config();
+            sw = APDU_RESPONSE_OK;
+            break;
+        default:
+            sw = APDU_RESPONSE_INVALID_P1_P2;
+            break;
+    }
+
+#ifdef HAVE_NBGL
+    if ((sw != APDU_RESPONSE_OK) ||
+        (g_icon_payload.received_size == g_icon_payload.expected_size)) {
+        explicit_bzero(&g_icon_payload, sizeof(g_icon_payload));
+    }
+#endif
+
+    return sw;
+}
diff --git a/src_features/provideDynamicNetwork/network_dynamic.h b/src_features/provideDynamicNetwork/network_dynamic.h
new file mode 100644
index 000000000..450087034
--- /dev/null
+++ b/src_features/provideDynamicNetwork/network_dynamic.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include <stdint.h>
+#include "network.h"
+
+#define MAX_DYNAMIC_NETWORKS 2  // Nb configurations max to store
+
+extern network_info_t DYNAMIC_NETWORK_INFO[];
+
+uint16_t handleNetworkConfiguration(uint8_t p1,
+                                    uint8_t p2,
+                                    const uint8_t *data,
+                                    uint8_t length,
+                                    unsigned int *tx);
diff --git a/src_features/provideNFTInformation/cmd_provideNFTInfo.c b/src_features/provideNFTInformation/cmd_provideNFTInfo.c
index 46bcfc97e..dc29dbddf 100644
--- a/src_features/provideNFTInformation/cmd_provideNFTInfo.c
+++ b/src_features/provideNFTInformation/cmd_provideNFTInfo.c
@@ -61,10 +61,6 @@ uint16_t handleProvideNFTInformation(const uint8_t *workBuffer,
 
     PRINTF("In handle provide NFTInformation\n");
 
-    if ((pluginType != ERC721) && (pluginType != ERC1155)) {
-        PRINTF("NFT metadata provided without proper plugin loaded!\n");
-        return APDU_RESPONSE_CONDITION_NOT_SATISFIED;
-    }
     nft = &get_current_asset_info()->nft;
 
     PRINTF("Provisioning currentAssetIndex %d\n", tmpCtx.transactionContext.currentAssetIndex);
diff --git a/src_features/provideTrustedName/cmd_provide_trusted_name.c b/src_features/provideTrustedName/cmd_provide_trusted_name.c
index f8dcabc92..de5aca30e 100644
--- a/src_features/provideTrustedName/cmd_provide_trusted_name.c
+++ b/src_features/provideTrustedName/cmd_provide_trusted_name.c
@@ -16,9 +16,6 @@
 #include "os_pki.h"
 #endif
 
-#define P1_FIRST_CHUNK     0x01
-#define P1_FOLLOWING_CHUNK 0x00
-
 #define STRUCT_TYPE_TRUSTED_NAME 0x03
 #define ALGO_SECP256K1           1
 
diff --git a/src_features/signMessageEIP712/field_hash.c b/src_features/signMessageEIP712/field_hash.c
index bec2ff43d..0818be856 100644
--- a/src_features/signMessageEIP712/field_hash.c
+++ b/src_features/signMessageEIP712/field_hash.c
@@ -174,16 +174,37 @@ static bool field_hash_domain_special_fields(const void *const field_ptr,
                                              uint8_t data_length) {
     const char *key;
     uint8_t keylen;
+    const char *ethermint_vc = "cosmos";
 
     key = get_struct_field_keyname(field_ptr, &keylen);
     // copy contract address into context
     if (strncmp(key, "verifyingContract", keylen) == 0) {
-        if (data_length != sizeof(eip712_context->contract_addr)) {
-            apdu_response_code = APDU_RESPONSE_INVALID_DATA;
-            PRINTF("Unexpected verifyingContract length!\n");
-            return false;
+        switch (struct_field_type(field_ptr)) {
+            case TYPE_SOL_ADDRESS:
+                if (data_length > sizeof(eip712_context->contract_addr)) {
+                    apdu_response_code = APDU_RESPONSE_INVALID_DATA;
+                    PRINTF("Error: verifyingContract too big\n");
+                    return false;
+                }
+                break;
+            case TYPE_SOL_STRING:
+                // hardcoded check for their non-standard implementation
+                if ((data_length != strlen(ethermint_vc)) ||
+                    (strncmp((char *) data, ethermint_vc, data_length) != 0)) {
+                    apdu_response_code = APDU_RESPONSE_INVALID_DATA;
+                    PRINTF("Error: non standard verifyingContract\n");
+                    return false;
+                }
+                break;
+            default:
+                apdu_response_code = APDU_RESPONSE_INVALID_DATA;
+                PRINTF("Error: unexpected type for verifyingContract (%u)!\n",
+                       struct_field_type(field_ptr));
+                return false;
         }
         memcpy(eip712_context->contract_addr, data, data_length);
+        explicit_bzero(&eip712_context->contract_addr[data_length],
+                       sizeof(eip712_context->contract_addr) - data_length);
     } else if (strncmp(key, "chainId", keylen) == 0) {
         eip712_context->chain_id = u64_from_BE(data, data_length);
     }
diff --git a/src_features/signMessageEIP712/ui_logic.c b/src_features/signMessageEIP712/ui_logic.c
index da0c2c4c4..d0462f61b 100644
--- a/src_features/signMessageEIP712/ui_logic.c
+++ b/src_features/signMessageEIP712/ui_logic.c
@@ -20,6 +20,7 @@
 #include "uint_common.h"
 #include "filtering.h"
 #include "trusted_name.h"
+#include "network.h"
 
 #define AMOUNT_JOIN_FLAG_TOKEN (1 << 0)
 #define AMOUNT_JOIN_FLAG_VALUE (1 << 1)
@@ -449,12 +450,14 @@ static bool ui_712_format_amount_join(void) {
         ismaxint(ui_ctx->amount.joins[ui_ctx->amount.idx].value,
                  ui_ctx->amount.joins[ui_ctx->amount.idx].value_length)) {
         strlcpy(strings.tmp.tmp, "Unlimited ", sizeof(strings.tmp.tmp));
-        strlcat(strings.tmp.tmp, (token != NULL) ? token->ticker : "???", sizeof(strings.tmp.tmp));
+        strlcat(strings.tmp.tmp,
+                (token != NULL) ? token->ticker : g_unknown_ticker,
+                sizeof(strings.tmp.tmp));
     } else {
         if (!amountToString(ui_ctx->amount.joins[ui_ctx->amount.idx].value,
                             ui_ctx->amount.joins[ui_ctx->amount.idx].value_length,
                             (token != NULL) ? token->decimals : 0,
-                            (token != NULL) ? token->ticker : "???",
+                            (token != NULL) ? token->ticker : g_unknown_ticker,
                             strings.tmp.tmp,
                             sizeof(strings.tmp.tmp))) {
             return false;
diff --git a/src_features/signTx/cmd_signTx.c b/src_features/signTx/cmd_signTx.c
index cd21300bc..e74f5971f 100644
--- a/src_features/signTx/cmd_signTx.c
+++ b/src_features/signTx/cmd_signTx.c
@@ -12,10 +12,6 @@ uint16_t handleSign(uint8_t p1,
     uint16_t sw = APDU_NO_RESPONSE;
     cx_err_t error = CX_INTERNAL_ERROR;
 
-    if (os_global_pin_is_validated() != BOLOS_UX_OK) {
-        PRINTF("Device is PIN-locked");
-        return APDU_RESPONSE_SECURITY_NOT_SATISFIED;
-    }
     if (p1 == P1_FIRST) {
         if (appState != APP_STATE_IDLE) {
             reset_app_context();
@@ -87,10 +83,11 @@ uint16_t handleSign(uint8_t p1,
                 // We have encountered an error while trying to sign a SWAP type transaction
                 // Return dedicated error code and flag an early exit back to Exchange
                 G_swap_response_ready = true;
-                return APDU_RESPONSE_MODE_CHECK_FAILED;
-            } else {
-                return APDU_RESPONSE_INVALID_DATA;
+                send_swap_error(ERROR_GENERIC, APP_CODE_CALLDATA_ISSUE, NULL, NULL);
+                // unreachable
+                os_sched_exit(0);
             }
+            return APDU_RESPONSE_INVALID_DATA;
         default:
             PRINTF("Unexpected parser status\n");
             return APDU_RESPONSE_INVALID_DATA;
diff --git a/src_features/signTx/feature_signTx.h b/src_features/signTx/feature_signTx.h
index ace67f0ae..f1c83df42 100644
--- a/src_features/signTx/feature_signTx.h
+++ b/src_features/signTx/feature_signTx.h
@@ -3,6 +3,22 @@
 
 #include "shared_context.h"
 
+// Error codes for swap, to be moved in SDK?
+#define ERROR_WRONG_AMOUNT            0x01
+#define ERROR_WRONG_DESTINATION       0x02
+#define ERROR_WRONG_FEES              0x03
+#define ERROR_WRONG_METHOD            0x04
+#define ERROR_CROSSCHAIN_WRONG_MODE   0x05
+#define ERROR_CROSSCHAIN_WRONG_METHOD 0x06
+#define ERROR_GENERIC                 0xFF
+
+// App codes for detail.
+typedef enum {
+    APP_CODE_DEFAULT = 0x00,
+    APP_CODE_CALLDATA_ISSUE = 0x01,
+    APP_CODE_NO_STANDARD_UI = 0x02
+} app_code_t;
+
 typedef enum {
 
     PLUGIN_UI_INSIDE = 0,
@@ -15,4 +31,6 @@ uint16_t finalizeParsing();
 void ux_approve_tx(bool fromPlugin);
 void start_signature_flow(void);
 
+void send_swap_error(uint8_t error_code, app_code_t app_code, const char *str1, const char *str2);
+
 #endif  // _SIGN_TX_H_
diff --git a/src_features/signTx/logic_signTx.c b/src_features/signTx/logic_signTx.c
index 7dfef1afb..b50642ebd 100644
--- a/src_features/signTx/logic_signTx.c
+++ b/src_features/signTx/logic_signTx.c
@@ -11,6 +11,8 @@
 #include "crypto_helpers.h"
 #include "format.h"
 #include "manage_asset_info.h"
+#include "handle_swap_sign_transaction.h"
+#include "os_math.h"
 
 static bool g_use_standard_ui;
 
@@ -323,6 +325,56 @@ static int strcasecmp_workaround(const char *str1, const char *str2) {
     return 0;
 }
 
+__attribute__((noreturn)) void send_swap_error(uint8_t error_code,
+                                               app_code_t app_code,
+                                               const char *str1,
+                                               const char *str2) {
+    uint32_t tx = 0;
+    uint len = 0;
+    PRINTF("APDU_RESPONSE_MODE_CHECK_FAILED: 0x%x\n", error_code);
+    // Set RAPDU error codes
+    G_io_apdu_buffer[tx++] = error_code;
+    G_io_apdu_buffer[tx++] = app_code;
+    // Set RAPDU error message
+    if (str1 != NULL) {
+        PRINTF("Expected %s\n", str1);
+        // If the string is too long, truncate it
+        len = MIN(strlen((const char *) str1), sizeof(G_io_apdu_buffer) - tx - 2);
+        memmove(G_io_apdu_buffer + tx, str1, len);
+        tx += len;
+        if (len < strlen((const char *) str1)) {
+            PRINTF("Truncated %s to %d bytes\n", str1, len);
+            G_io_apdu_buffer[tx - 1] = '*';
+        }
+    }
+    if (str2 != NULL) {
+        PRINTF("Received %s\n", str2);
+        // Do we have enough space to add a separator?
+        if ((tx + 1 + 2) < sizeof(G_io_apdu_buffer)) {
+            G_io_apdu_buffer[tx++] = '#';
+        }
+        // Do we have enough space to add at least one character?
+        if ((tx + 1 + 2) < sizeof(G_io_apdu_buffer)) {
+            // If the string is too long, truncate it
+            len = MIN(strlen((const char *) str2), sizeof(G_io_apdu_buffer) - tx - 2);
+            memmove(G_io_apdu_buffer + tx, str2, len);
+            tx += len;
+            if (len < strlen((const char *) str2)) {
+                PRINTF("Truncated %s to %d bytes\n", str2, len);
+                G_io_apdu_buffer[tx - 1] = '*';
+            }
+        }
+    }
+    // Set RAPDU status word, with previous check we are sure there is at least 2 bytes left
+    U2BE_ENCODE(G_io_apdu_buffer, tx, APDU_RESPONSE_MODE_CHECK_FAILED);
+    tx += 2;
+    // Send RAPDU
+    io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, tx);
+    // In case of success, the apdu is sent immediately and eth exits
+    // Reaching this code means we encountered an error
+    finalize_exchange_sign_transaction(false);
+}
+
 __attribute__((noinline)) static uint16_t finalize_parsing_helper(void) {
     char displayBuffer[50];
     uint8_t decimals = WEI_TO_ETHER;
@@ -453,19 +505,20 @@ __attribute__((noinline)) static uint16_t finalize_parsing_helper(void) {
         G_swap_response_ready = true;
     }
 
-    // User has just validated a swap but ETH received apdus about a non standard plugin / contract
-    if (G_called_from_swap && !g_use_standard_ui) {
-        PRINTF("APDU_RESPONSE_MODE_CHECK_FAILED, G_called_from_swap\n");
-        return APDU_RESPONSE_MODE_CHECK_FAILED;
-    }
-
-    // Specific calldata check when in swap mode
     if (G_called_from_swap) {
+        // User has just validated a swap but ETH received apdus about a non standard plugin /
+        // contract
+        if (!g_use_standard_ui) {
+            send_swap_error(ERROR_WRONG_METHOD, APP_CODE_NO_STANDARD_UI, NULL, NULL);
+            // unreachable
+            os_sched_exit(0);
+        }
         // Two success cases: we are in standard mode and no calldata was received
         // We are in crosschain mode and the correct calldata has been received
         if (G_swap_mode != SWAP_MODE_STANDARD && G_swap_mode != SWAP_MODE_CROSSCHAIN_SUCCESS) {
-            PRINTF("Error: G_swap_mode %d refused\n", G_swap_mode);
-            return APDU_RESPONSE_MODE_CHECK_FAILED;
+            send_swap_error(ERROR_CROSSCHAIN_WRONG_MODE, APP_CODE_DEFAULT, NULL, NULL);
+            // unreachable
+            os_sched_exit(0);
         }
     }
 
@@ -491,8 +544,12 @@ __attribute__((noinline)) static uint16_t finalize_parsing_helper(void) {
         if (G_called_from_swap) {
             // Ensure the values are the same that the ones that have been previously validated
             if (strcasecmp_workaround(strings.common.toAddress, displayBuffer) != 0) {
-                PRINTF("APDU_RESPONSE_MODE_CHECK_FAILED, address check failed\n");
-                return APDU_RESPONSE_MODE_CHECK_FAILED;
+                send_swap_error(ERROR_WRONG_DESTINATION,
+                                APP_CODE_DEFAULT,
+                                strings.common.toAddress,
+                                displayBuffer);
+                // unreachable
+                os_sched_exit(0);
             }
         } else {
             strlcpy(strings.common.toAddress, displayBuffer, sizeof(strings.common.toAddress));
@@ -514,10 +571,12 @@ __attribute__((noinline)) static uint16_t finalize_parsing_helper(void) {
         if (G_called_from_swap) {
             // Ensure the values are the same that the ones that have been previously validated
             if (strcmp(strings.common.fullAmount, displayBuffer) != 0) {
-                PRINTF("APDU_RESPONSE_MODE_CHECK_FAILED, amount check failed\n");
-                PRINTF("Expected %s\n", strings.common.fullAmount);
-                PRINTF("Received %s\n", displayBuffer);
-                return APDU_RESPONSE_MODE_CHECK_FAILED;
+                send_swap_error(ERROR_WRONG_AMOUNT,
+                                APP_CODE_DEFAULT,
+                                strings.common.fullAmount,
+                                displayBuffer);
+                // unreachable
+                os_sched_exit(0);
             }
         } else {
             strlcpy(strings.common.fullAmount, displayBuffer, sizeof(strings.common.fullAmount));
@@ -536,10 +595,12 @@ __attribute__((noinline)) static uint16_t finalize_parsing_helper(void) {
     if (G_called_from_swap) {
         // Ensure the values are the same that the ones that have been previously validated
         if (strcmp(strings.common.maxFee, displayBuffer) != 0) {
-            PRINTF("APDU_RESPONSE_MODE_CHECK_FAILED, fees check failed\n");
-            PRINTF("Expected %s\n", strings.common.maxFee);
-            PRINTF("Received %s\n", displayBuffer);
-            return APDU_RESPONSE_MODE_CHECK_FAILED;
+            send_swap_error(ERROR_WRONG_FEES,
+                            APP_CODE_DEFAULT,
+                            strings.common.maxFee,
+                            displayBuffer);
+            // unreachable
+            os_sched_exit(0);
         }
     } else {
         strlcpy(strings.common.maxFee, displayBuffer, sizeof(strings.common.maxFee));
diff --git a/src_nbgl/network_icons.c b/src_nbgl/network_icons.c
index 8f7a4532e..ccd1a4090 100644
--- a/src_nbgl/network_icons.c
+++ b/src_nbgl/network_icons.c
@@ -1,5 +1,7 @@
 #include "os_utils.h"
 #include "os_pic.h"
+#include "network.h"
+#include "network_dynamic.h"
 #include "net_icons.gen.h"
 
 /**
@@ -11,8 +13,16 @@
  * @return the network icon if found, \ref NULL otherwise
  */
 const nbgl_icon_details_t *get_network_icon_from_chain_id(const uint64_t *chain_id) {
+    for (size_t i = 0; i < MAX_DYNAMIC_NETWORKS; ++i) {
+        if ((DYNAMIC_NETWORK_INFO[i].chain_id == *chain_id) &&
+            (DYNAMIC_NETWORK_INFO[i].icon.bitmap != NULL)) {
+            PRINTF("[NETWORK_ICONS] - Found dynamic %s\n", DYNAMIC_NETWORK_INFO[i].name);
+            return PIC(&DYNAMIC_NETWORK_INFO[i].icon);
+        }
+    }
     for (size_t i = 0; i < ARRAYLEN(g_network_icons); ++i) {
         if ((uint64_t) PIC(g_network_icons[i].chain_id) == *chain_id) {
+            PRINTF("[NETWORK_ICONS] - Fallback on hardcoded list.\n");
             return PIC(g_network_icons[i].icon);
         }
     }
diff --git a/src_plugins/erc20/erc20_plugin.c b/src_plugins/erc20/erc20_plugin.c
index 2dd28f8d1..aea505e31 100644
--- a/src_plugins/erc20/erc20_plugin.c
+++ b/src_plugins/erc20/erc20_plugin.c
@@ -189,7 +189,7 @@ void erc20_plugin_call(int message, void *parameters) {
                                             context->decimals,
                                             context->ticker,
                                             msg->msg,
-                                            100)) {
+                                            msg->msgLength)) {
                             msg->result = ETH_PLUGIN_RESULT_ERROR;
                             break;
                         }
diff --git a/src_plugins/eth2/eth2_plugin.c b/src_plugins/eth2/eth2_plugin.c
index 7cc3269aa..29739fcef 100644
--- a/src_plugins/eth2/eth2_plugin.c
+++ b/src_plugins/eth2/eth2_plugin.c
@@ -206,7 +206,7 @@ void eth2_plugin_call(int message, void *parameters) {
                                         decimals,
                                         ticker,
                                         msg->msg,
-                                        100)) {
+                                        msg->msgLength)) {
                         msg->result = ETH_PLUGIN_RESULT_ERROR;
                         break;
                     }
diff --git a/tools/gen_dynamic_network.py b/tools/gen_dynamic_network.py
new file mode 100755
index 000000000..42135830d
--- /dev/null
+++ b/tools/gen_dynamic_network.py
@@ -0,0 +1,286 @@
+#!/usr/bin/env python3
+
+import os
+import subprocess
+import sys
+import struct
+import logging
+import re
+from hashlib import sha256
+from enum import IntEnum
+from typing import List, Optional
+from pathlib import Path
+import argparse
+
+# Resolve the parent directory and append to sys.path
+parent = Path(__file__).parent.parent.resolve()
+client_module = Path(f"{parent}/client/src/ledger_app_clients/ethereum")
+sys.path.append(f"{client_module}")
+# Import the required module
+from tlv import format_tlv  # type: ignore
+from keychain import Key, sign_data   # type: ignore
+
+# Retrieve the SDK path from the environment variable
+sdk_path = os.getenv('BOLOS_SDK')
+if sdk_path:
+    # Import the library dynamically
+    sys.path.append(f"{sdk_path}/lib_nbgl/tools")
+    from icon2glyph import open_image, compute_app_icon_data  # type: ignore
+else:
+    print("Environment variable BOLOS_SDK is not set")
+    sys.exit(1)
+
+
+class NetworkInfoTag(IntEnum):
+    STRUCTURE_TYPE = 0x01
+    STRUCTURE_VERSION = 0x02
+    BLOCKCHAIN_FAMILY = 0x51
+    CHAIN_ID = 0x23
+    NETWORK_NAME = 0x52
+    TICKER = 0x24
+    NETWORK_ICON_HASH = 0x53
+    DER_SIGNATURE = 0x15
+
+
+class P1Type(IntEnum):
+    FIRST_CHUNK = 0x01
+    NEXT_CHUNK = 0x00
+
+
+class P2Type(IntEnum):
+    NETWORK_CONFIG = 0x00
+    NETWORK_ICON = 0x01
+    GET_INFO = 0x02
+
+
+PROVIDE_NETWORK_INFORMATION = 0x30
+CLA = 0xE0
+
+logger = logging.getLogger(__name__)
+
+
+# ===============================================================================
+#          Parameters
+# ===============================================================================
+def init_parser() -> argparse.ArgumentParser:
+    parser = argparse.ArgumentParser(description="Generate hex string for network icon, in NBGL format.")
+    parser.add_argument("--icon", "-i", help="Input icon to process.")
+    parser.add_argument("--name", "-n", required=True, help="Network name")
+    parser.add_argument("--ticker", "-t", required=True, help="Network ticker")
+    parser.add_argument("--chainid", "-c", type=int, required=True, help="Network chain_id")
+    parser.add_argument("--verbose", "-v", action='store_true', help="Verbose mode")
+    return parser
+
+
+# ===============================================================================
+#          Logging
+# ===============================================================================
+def set_logging(verbose: bool = False) -> None:
+    if verbose:
+        logger.setLevel(level=logging.DEBUG)
+    else:
+        logger.setLevel(level=logging.INFO)
+    logger.handlers.clear()
+    handler = logging.StreamHandler()
+    handler.setFormatter(logging.Formatter("[%(levelname)s] %(message)s"))
+    logger.addHandler(handler)
+
+
+# ===============================================================================
+#          Check icon - Extracted from ledger-app-workflow/scripts/check_icon.sh
+# ===============================================================================
+def check_glyph(file: str) -> bool:
+    extension = os.path.splitext(file)[1][1:]
+    if extension not in ["gif", "bmp", "png"]:
+        logger.error(f"Glyph extension should be '.gif', '.bmp', or '.png', not '.{extension}'")
+        return False
+
+    try:
+        content = subprocess.check_output(["identify", "-verbose", file], text=True)
+    except subprocess.CalledProcessError as e:
+        logger.error(f"Failed to identify file: {e}")
+        return False
+
+    if "Alpha" in content:
+        logger.error("Glyph should have no alpha channel")
+        return False
+
+    x = re.search(r"Colors: (.*)", content)
+    if x is None:
+            logger.error("Glyph should have the colors defined")
+            return False
+    nb_colors = int(x.group(1))
+    if "Type: Bilevel" in content:
+        logger.debug("Monochrome image type")
+
+        if nb_colors != 2:
+            logger.error("Glyph should have only 2 colors")
+            return False
+
+        if re.search(r"0.*0.*0.*black", content) is None:
+            logger.error("Glyph should have the black color defined")
+            return False
+
+        if re.search(r"255.*255.*255.*white", content) is None:
+            logger.error("Glyph should have the white color defined")
+            return False
+
+        if not any(depth in content for depth in ["Depth: 1-bit", "Depth: 8/1-bit"]):
+            logger.error("Glyph should have 1 bit depth")
+            return False
+
+    elif "Type: Grayscale" in content:
+        logger.debug("Grayscale image type")
+
+        if nb_colors > 16:
+            logger.error(f"4bpp glyphs can't have more than 16 colors, {nb_colors} found")
+            return False
+
+        if not any(depth in content for depth in ["Depth: 8-bit", "Depth: 8/8-bit"]):
+            logger.error("Glyph should have 8 bits depth")
+            return False
+
+    else:
+        logger.error("Glyph should be Monochrome or Grayscale")
+        return False
+
+    logger.info(f"Glyph '{file}' is compliant")
+    return True
+
+
+# ===============================================================================
+#          Prepare APDU - Extracted from python client
+# ===============================================================================
+def serialize(p1: int, p2: int, cdata: bytes) -> bytes:
+    """
+    Serializes the provided network information into byte format.
+
+    Args:
+        p1 (int): P1 parameter to be included in the header.
+        p2 (int): P2 parameter to be included in the header.
+        cdata (bytes): The network information data to be serialized.
+
+    Returns:
+        bytes: The serialized byte sequence combining the header and the data.
+    """
+
+    # Initialize a bytearray to construct the header
+    header = bytearray()
+    header.append(CLA)
+    header.append(PROVIDE_NETWORK_INFORMATION)
+    header.append(p1)
+    header.append(p2)
+    header.append(len(cdata))
+    # Return the concatenation of the header and cdata
+    return header + cdata
+
+
+def generate_tlv_payload(name: str,
+                         ticker: str,
+                         chain_id: int,
+                         icon: Optional[bytes] = None) -> bytes:
+    """
+    Generates a TLV (Type-Length-Value) payload for the given network information.
+
+    Args:
+        name (str): The name of the blockchain network.
+        ticker (str): The ticker symbol of the blockchain network.
+        chain_id (int): The unique identifier for the blockchain network.
+        icon (Optional[bytes]): Optional icon data in bytes for the blockchain network.
+
+    Returns:
+        bytes: The generated TLV payload.
+    """
+
+    payload: bytes = format_tlv(NetworkInfoTag.STRUCTURE_TYPE, 8)
+    payload += format_tlv(NetworkInfoTag.STRUCTURE_VERSION, 1)
+    payload += format_tlv(NetworkInfoTag.BLOCKCHAIN_FAMILY, 1)
+    payload += format_tlv(NetworkInfoTag.CHAIN_ID, chain_id.to_bytes(8, 'big'))
+    payload += format_tlv(NetworkInfoTag.NETWORK_NAME, name.encode('utf-8'))
+    payload += format_tlv(NetworkInfoTag.TICKER, ticker.encode('utf-8'))
+    if icon:
+        # Network Icon Hash
+        payload += format_tlv(NetworkInfoTag.NETWORK_ICON_HASH, sha256(icon).digest())
+    # Append the data Signature
+    payload += format_tlv(NetworkInfoTag.DER_SIGNATURE,
+                              sign_data(Key.CAL, payload))
+
+    # Return the constructed TLV payload as bytes
+    return payload
+
+
+def prepare_network_information(name: str,
+                                ticker: str,
+                                chain_id: int,
+                                icon: Optional[bytes] = None) -> List[bytes]:
+    """
+    Prepares network information for a given blockchain network.
+
+    Args:
+        name (str): The name of the blockchain network.
+        ticker (str): The ticker symbol of the blockchain network.
+        chain_id (int): The unique identifier for the blockchain network.
+        icon (Optional[bytes]): Optional icon data in bytes for the blockchain network.
+
+    Returns:
+        List[bytes]: A list of byte chunks representing the network information.
+    """
+
+    # Initialize an empty list to store the byte chunks
+    chunks: List[bytes] = []
+    # Generate the TLV payload
+    payload = generate_tlv_payload(name, ticker, chain_id, icon)
+    # Check if the payload is larger than 0xff
+    assert len(payload) < 0xff, "Payload too large"
+    # Serialize the payload
+    chunks.append(serialize(0x00, P2Type.NETWORK_CONFIG, payload))
+
+    if icon:
+        # Serialize the icon
+        p1 = P1Type.FIRST_CHUNK
+        while len(icon) > 0:
+            chunks.append(serialize(p1, P2Type.NETWORK_ICON, icon[:0xff]))
+            icon = icon[0xff:]
+            p1 = P1Type.NEXT_CHUNK
+    return chunks
+
+
+# ===============================================================================
+#          Main entry
+# ===============================================================================
+def main() -> None:
+    parser = init_parser()
+    args = parser.parse_args()
+
+    set_logging(args.verbose)
+
+    if args.icon:
+        if not os.access(args.icon, os.R_OK):
+            logger.error(f"Cannot read file {args.icon}")
+            sys.exit(1)
+
+        # Open image in luminance format
+        im, bpp = open_image(args.icon)
+        if im is None:
+            logger.error(f"Unable to access icon file {args.icon}")
+            sys.exit(1)
+
+        # Check icon
+        if not check_glyph(args.icon):
+            logger.error(f"Invalid icon file {args.icon}")
+            sys.exit(1)
+
+        # Prepare and print app icon data
+        _, image_data = compute_app_icon_data(False, im, bpp, False)
+        logger.debug(f"image_data={image_data.hex()}")
+    else:
+        image_data = None
+
+    chunks = prepare_network_information(args.name, args.ticker, args.chainid, image_data)
+    # Print each chunk with its index
+    for i, chunk in enumerate(chunks):
+        logger.info(f"Chunk {i}[{len(chunk)}]: {chunk.hex()}")
+
+
+if __name__ == "__main__":
+    main()


Test patch:
diff --git a/tests/ragger/test_get_address.py b/tests/ragger/test_get_address.py
index 035c598c5..39e0ab16a 100644
--- a/tests/ragger/test_get_address.py
+++ b/tests/ragger/test_get_address.py
@@ -52,6 +52,24 @@ def test_get_pk(backend: BackendInterface,
                 chain: Optional[int]):
     app_client = EthAppClient(backend)
 
+    if chain == 5:
+        name = "Goerli"
+        ticker = "ETH"
+        # pylint: disable=line-too-long
+        icon = "400040000195000093001f8b08000000000002ff85ceb10dc4300805d01fb970e9113c4a467346cb288ce092223ace80382b892ef9cd93ac0f0678881c4616d980b4bb99aa0801a5874d844ff695b5d7f6c23ad79058f79c8df7e8c5dc7d9fff13ffc61d71d7bcf32549bcef5672c5a430bb1cd6073f68c3cd3d302cd3feea88f547d6a99eb8e87ecbcdecd255b8f869033cfd932feae0c09000020000"
+        # pylint: enable=line-too-long
+    elif chain == 137:
+        name = "Polygon"
+        ticker = "POL"
+        # pylint: disable=line-too-long
+        icon = "400040002188010086011f8b08000000000002ffbd55416ac33010943018422f0e855c42fee01e4b09f94a7a0c3df82b3905440ffd4243a1507ae81b4c1e9047180a2604779d58f6ae7664e8250b0ec613ad7646b3aba6b949fc9a2eee8f105f7bdc14083e653d3e1f4d6f4c82f0ed809b3780e70c5f6ab8a6cf8f1b474175a41a2f8db1d7b47b7a3b2276c9506881d8cd87d7bb10afd8a2356048ec12bfe90130cc59d12d9585166f05ffdcb363295b863f21bb54662bfe85cb8ca522402bec2a92ad8d336936e9b50416e19a55e000f837d2d2a2e3f79a7d39f78aec938eb9bf549ae91378fa16a118ca982ea3febe46aa18ca90f59c14ce1c21fbd3c744e087cc582a921e7bf9552a4a761fb1461f39c5114f75f1b9732f5117499f484f813ec84386416f6f75a30b17689fd519ef3cd6f3b83114c300a7dd66b129a276d3a3a3d20209cdc09ce1fac039c50480738e09b8471dc116c18e1a18d6784eb7453bb773ee19cff9d29b37c3f744cdfc0dedc7ee0968dff7487fe97b0acf8bf3c3b48be236f772f307b129c97400080000"
+        # pylint: enable=line-too-long
+    else:
+        name = ""
+
+    if name:
+        app_client.provide_network_information(name, ticker, chain, bytes.fromhex(icon))
+
     with app_client.get_public_addr(chaincode=with_chaincode, chain_id=chain):
         scenario_navigator.address_review_approve(test_name=f"get_pk_{chain}")
 
diff --git a/tests/ragger/test_nft.py b/tests/ragger/test_nft.py
index ed3b08ff1..d80f8a03a 100644
--- a/tests/ragger/test_nft.py
+++ b/tests/ragger/test_nft.py
@@ -60,6 +60,24 @@ def common_test_nft(firmware: Firmware,
     if firmware == Firmware.NANOS:
         pytest.skip("Not supported on LNS")
 
+    if collec.chain_id == 5:
+        name = "Goerli"
+        ticker = "ETH"
+        # pylint: disable=line-too-long
+        icon = "400040000195000093001f8b08000000000002ff85ceb10dc4300805d01fb970e9113c4a467346cb288ce092223ace80382b892ef9cd93ac0f0678881c4616d980b4bb99aa0801a5874d844ff695b5d7f6c23ad79058f79c8df7e8c5dc7d9fff13ffc61d71d7bcf32549bcef5672c5a430bb1cd6073f68c3cd3d302cd3feea88f547d6a99eb8e87ecbcdecd255b8f869033cfd932feae0c09000020000"
+        # pylint: enable=line-too-long
+    elif collec.chain_id == 137:
+        name = "Polygon"
+        ticker = "POL"
+        # pylint: disable=line-too-long
+        icon = "400040002188010086011f8b08000000000002ffbd55416ac33010943018422f0e855c42fee01e4b09f94a7a0c3df82b3905440ffd4243a1507ae81b4c1e9047180a2604779d58f6ae7664e8250b0ec613ad7646b3aba6b949fc9a2eee8f105f7bdc14083e653d3e1f4d6f4c82f0ed809b3780e70c5f6ab8a6cf8f1b474175a41a2f8db1d7b47b7a3b2276c9506881d8cd87d7bb10afd8a2356048ec12bfe90130cc59d12d9585166f05ffdcb363295b863f21bb54662bfe85cb8ca522402bec2a92ad8d336936e9b50416e19a55e000f837d2d2a2e3f79a7d39f78aec938eb9bf549ae91378fa16a118ca982ea3febe46aa18ca90f59c14ce1c21fbd3c744e087cc582a921e7bf9552a4a761fb1461f39c5114f75f1b9732f5117499f484f813ec84386416f6f75a30b17689fd519ef3cd6f3b83114c300a7dd66b129a276d3a3a3d20209cdc09ce1fac039c50480738e09b8471dc116c18e1a18d6784eb7453bb773ee19cff9d29b37c3f744cdfc0dedc7ee0968dff7487fe97b0acf8bf3c3b48be236f772f307b129c97400080000"
+        # pylint: enable=line-too-long
+    else:
+        name = ""
+
+    if name:
+        app_client.provide_network_information(name, ticker, collec.chain_id, bytes.fromhex(icon))
+
     if DEVICE_ADDR is None:  # to only have to request it once
         with app_client.get_public_addr(display=False):
             pass
diff --git a/tests/ragger/test_sign.py b/tests/ragger/test_sign.py
index f7b720bf2..499ed651c 100644
--- a/tests/ragger/test_sign.py
+++ b/tests/ragger/test_sign.py
@@ -41,6 +41,24 @@ def common(firmware: Firmware,
            confirm: bool = False):
     app_client = EthAppClient(backend)
 
+    if tx_params["chainId"] == 3:
+        name = "Ropsten"
+        ticker = "ETH"
+        # pylint: disable=line-too-long
+        icon = "4000400021bd0100bb011f8b08000000000002ffed94bf4bc34014c7ff8643514441f2829b20e405a50e0eed26d242dd2df40f30505b6d3bb8ba74b260373b38386670a85b978274ea1f211d9cb228356df079975c9aa44db22bfd4ec97d8ef7e37bf78e68a57fa469894629f8a7b845032b99df2b8c2699443c00851994bb4dc06300c676a8adc797606b82afd327c66698654170d67710f331a5973956383fa073d4bb4bbc0d926fd23be291b5543a78e119b36c443c8be20904bc4439be2192c1ce821f9eb16d7ae25c0f65708a10e26bf4c5399e06bca285c233d67504c74b1fbf0044f82e553094610c0b7c835b28e4d9f8adf958919c8d6c97e385a82d0b4bbce076c833989c776e7c9551aa44de625d1ce46cceaf38d25415d1926b9e47b6cf6b826a9a7628979ab2bfa1bf4186cfd3b3f86bcc1dec495e14544593dc3f3330b0332f80a7d7bdf0add0f9c826aa08a8e13ebd89e8d1b970f9b59bde70c3f5a317e0c3dd90e518dcee1e17efd7abe710408626416b21899eaa8058e05ff598091035d6787cd3f18d5b1c00e190bac7bb6b260cb770e8847a8da4091e72870cbaeb260e70afa65bd356ca03f1704c76dafbe218a9efcb4a7f4dbf34cf947a00080000"
+        # pylint: enable=line-too-long
+    elif tx_params["chainId"] == 56:
+        name = "BSC"
+        ticker = "BNB"
+        # pylint: disable=line-too-long
+        icon = "4000400021ff0100fd011f8b08000000000002ffdd554b6ac330101d61f0225090307861c81d5c0c5d1482afe20b945c293941aed2e05d21e810012d0c42d58c64eb63d7dd475944d14833f3deccbc18f322ebf190c6e8875f32373f016a6314e32000b8e873fb0580dd8c69c1ad32336b6e0f7bba468b6501141e1e288c5be7d47e07a8aa0feba77bc70fc031b7c71e6db422f75f67d9ded20b6d8268a4649317c9fd69857022f21c85f6e790e4a3683f0a61112a214e84274278a5fd005048ccbd243e0e51720d46e08e960189d66d8470a26488c223e57e4e118eb4bff8ba4ca26a288d258121d8712311809652063617ffd0af3b47b96a5089578527b2d98c6f5d7844e70fef14e08bfacfcc44126ea48d4847070297757815e2166a75899acfb712774de2d0617dafc1cec86fbda0539e7488fa8f88f66ea8bf1c42b7dee8ac0f762c5df4fe38dba77ffcfbaff6148663c9aff4b460ee6b7c7e082c2dcd8a9f3ef0639f157bfc62d8ddfa2860a1bef5babe737fa09d6df4874748ece3466b7a25636d99fbafc0fe149ff6ac0b1a33f1d2786958684d2698e64327f39128c0325f8c8ae44ad144088d9fcf667b3edd7c9b68be674e4388541f783e08a9be3cf34152e9fd21d7a795be65eebfad3e76561fa7aeeb2ad4c7665f5fd9795f9f0bb9a9effa2f7d37231748abe08201dbf87ff0e2f843ddf7f378957f4df30b82b2cf8e00080000"
+        # pylint: enable=line-too-long
+    else:
+        name = ""
+
+    if name:
+        app_client.provide_network_information(name, ticker, tx_params["chainId"], bytes.fromhex(icon))
+
     with app_client.get_public_addr(bip32_path=path, display=False):
         pass
     _, DEVICE_ADDR, _ = ResponseParser.pk_addr(app_client.response().data)
diff --git a/tests/ragger/test_trusted_name.py b/tests/ragger/test_trusted_name.py
index 5f7468a19..fc28718ce 100644
--- a/tests/ragger/test_trusted_name.py
+++ b/tests/ragger/test_trusted_name.py
@@ -119,6 +119,15 @@ def test_trusted_name_v1_non_mainnet(firmware: Firmware,
     app_client = EthAppClient(backend)
     challenge = common(firmware, app_client)
 
+    chain_id = 5
+    # pylint: disable=line-too-long
+    icon = "400040000195000093001f8b08000000000002ff85ceb10dc4300805d01fb970e9113c4a467346cb288ce092223ace80382b892ef9cd93ac0f0678881c4616d980b4bb99aa0801a5874d844ff695b5d7f6c23ad79058f79c8df7e8c5dc7d9fff13ffc61d71d7bcf32549bcef5672c5a430bb1cd6073f68c3cd3d302cd3feea88f547d6a99eb8e87ecbcdecd255b8f869033cfd932feae0c09000020000"
+    # pylint: enable=line-too-long
+    app_client.provide_network_information("Goerli",
+                                           "ETH",
+                                           chain_id,
+                                           bytes.fromhex(icon))
+
     app_client.provide_trusted_name_v1(ADDR, NAME, challenge)
 
     with app_client.sign(BIP32_PATH,
@@ -128,7 +137,7 @@ def test_trusted_name_v1_non_mainnet(firmware: Firmware,
                              "gas": GAS_LIMIT,
                              "to": ADDR,
                              "value": Web3.to_wei(AMOUNT, "ether"),
-                             "chainId": 5
+                             "chainId": chain_id
                          }):
         if firmware.is_nano:
             end_text = "Accept"