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

Issue:
NFT app version adjustment
- Bring the develop branch back in sync with master (some commits were only present on master)
- Linting pass on the NFT code (courtesy of @lpascal-ledger)
- Bump the app version to 1.9.17

A previous similar PR (#232) was done but was mistakenly squashed resulting in develop still being out-of-sync with master.


Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e429752cf..9b72ae0c1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,13 +5,40 @@ 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.9.17](https://github.com/ledgerhq/app-ethereum/compare/1.9.16...1.9.17) - 2022-01-14
+
+### Added
+
+- Support for Non-Fungible Tokens (ERC-721 & ERC-1155)
+
+## [1.9.16](https://github.com/ledgerhq/app-ethereum/compare/1.9.14...1.9.16) - 2022-01-13
+
+### Added
+
+- Shyft variant
+
+## [1.9.14](https://github.com/ledgerhq/app-ethereum/compare/1.9.13...1.9.14) - 2021-11-30
+
+### Added
+
+- Added Moonriver BIP44 1285
+
+### Fixed
+
+- Fixed stark order signature on LNS 
+
+## [1.9.13](https://github.com/ledgerhq/app-ethereum/compare/1.9.12...1.9.13) - 2021-11-17
+
+### Changed
+
+- Small improvement in app size
+
 ## [1.9.12](https://github.com/ledgerhq/app-ethereum/compare/1.9.11...1.9.12) - 2021-11-12
 
 ### Fixed
 
 - Fixed stark order signature on LNX 
 
-
 ## [1.9.11](https://github.com/ledgerhq/app-ethereum/compare/1.9.10...1.9.11) - 2021-10-12
 
 ### Added
diff --git a/Makefile b/Makefile
old mode 100755
new mode 100644
index d65af42e9..0a1a20910
--- a/Makefile
+++ b/Makefile
@@ -30,7 +30,7 @@ APP_LOAD_PARAMS += --path "1517992542'/1101353413'"
 
 APPVERSION_M=1
 APPVERSION_N=9
-APPVERSION_P=16
+APPVERSION_P=17
 APPVERSION=$(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P)
 APP_LOAD_FLAGS= --appFlags 0x240 --dep Ethereum:$(APPVERSION)
 
@@ -248,10 +248,10 @@ APP_LOAD_PARAMS += $(APP_LOAD_FLAGS) --path "44'/1'"
 DEFINES += $(DEFINES_LIB)
 
 #prepare hsm generation
-ifeq ($(TARGET_NAME), TARGET_NANOX)
-ICONNAME=icons/nanox_app_$(CHAIN).gif
-else
+ifeq ($(TARGET_NAME),TARGET_NANOS)
 ICONNAME=icons/nanos_app_$(CHAIN).gif
+else
+ICONNAME=icons/nanox_app_$(CHAIN).gif
 endif
 
 ################
@@ -283,19 +283,21 @@ DEFINES   += HAVE_UX_FLOW
 DEFINES   += HAVE_WEBUSB WEBUSB_URL_SIZE_B=0 WEBUSB_URL=""
 
 ifeq ($(TARGET_NAME),TARGET_NANOX)
-DEFINES   += IO_SEPROXYHAL_BUFFER_SIZE_B=300
 DEFINES   += HAVE_BLE BLE_COMMAND_TIMEOUT_MS=2000
 DEFINES   += HAVE_BLE_APDU # basic ledger apdu transport over BLE
+endif
 
+ifeq ($(TARGET_NAME),TARGET_NANOS)
+DEFINES   += IO_SEPROXYHAL_BUFFER_SIZE_B=72
+DEFINES   += HAVE_WALLET_ID_SDK
+else
+DEFINES   += IO_SEPROXYHAL_BUFFER_SIZE_B=300
 DEFINES   += HAVE_GLO096
 DEFINES   += HAVE_BAGL BAGL_WIDTH=128 BAGL_HEIGHT=64
 DEFINES   += HAVE_BAGL_ELLIPSIS # long label truncation feature
 DEFINES   += HAVE_BAGL_FONT_OPEN_SANS_REGULAR_11PX
 DEFINES   += HAVE_BAGL_FONT_OPEN_SANS_EXTRABOLD_11PX
 DEFINES   += HAVE_BAGL_FONT_OPEN_SANS_LIGHT_16PX
-else
-DEFINES   += IO_SEPROXYHAL_BUFFER_SIZE_B=72
-DEFINES   += HAVE_WALLET_ID_SDK
 endif
 
 # Enables direct data signing without having to specify it in the settings. Useful when testing with speculos.
@@ -304,21 +306,31 @@ ifneq ($(ALLOW_DATA),0)
 DEFINES += HAVE_ALLOW_DATA
 endif
 
-# Bypass the signature verification for setExternalPlugin and provideERC20TokenInfo calls
+# Bypass the signature verification for setExternalPlugin, setPlugin, provideERC20TokenInfo and provideNFTInfo calls
 BYPASS_SIGNATURES:=0
 ifneq ($(BYPASS_SIGNATURES),0)
 DEFINES += HAVE_BYPASS_SIGNATURES
 endif
 
+# NFTs
+ifneq ($(TARGET_NAME),TARGET_NANOS)
+DEFINES	+= HAVE_NFT_SUPPORT
+# Enable the NFT testing key
+NFT_TESTING_KEY:=0
+ifneq ($(NFT_TESTING_KEY),0)
+DEFINES += HAVE_NFT_TESTING_KEY
+endif
+endif
+
 
 # Enabling debug PRINTF
 DEBUG:=0
 ifneq ($(DEBUG),0)
 DEFINES += HAVE_STACK_OVERFLOW_CHECK
-ifeq ($(TARGET_NAME),TARGET_NANOX)
-DEFINES   += HAVE_PRINTF PRINTF=mcu_usb_printf
-else
+ifeq ($(TARGET_NAME),TARGET_NANOS)
 DEFINES   += HAVE_PRINTF PRINTF=screen_printf
+else
+DEFINES   += HAVE_PRINTF PRINTF=mcu_usb_printf
 endif
 else
 DEFINES   += PRINTF\(...\)=
diff --git a/README.md b/README.md
index 417c5bb21..5fd6b9a3a 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@ Ledger Blue is not maintained anymore, but the app can still be compiled for thi
 
 This app follows the specification available in the `doc/` folder.
 
-To compile it and load it on a device, please check out our [developer portal](https://developers.ledger.com/docs/NA/start_here/).
+To compile it and load it on a device, please check out our [developer portal](https://developers.ledger.com/docs/nano-app/introduction/).
 
 # Plugins
 
@@ -20,7 +20,7 @@ First [install yarn](https://classic.yarnpkg.com/en/docs/install/#debian-stable)
 Open `tests/build_local_test_elfs.sh` and add your BOLOS SDKs path to `NANOS_SDK` and `NANOX_SDK`.
 This helper script will build the applications required by the test suite and move them at the right place.
 ```
-cd test
+cd tests
 ./build_local_test_elfs.sh
 ```
 Then you can install the project by simply running:
diff --git a/doc/ethapp.asc b/doc/ethapp.asc
index b3c01fcaf..53007b480 100644
--- a/doc/ethapp.asc
+++ b/doc/ethapp.asc
@@ -23,6 +23,9 @@ Application version 1.5.0 - 25th of September 2020
 ## 1.7.6
   - Add SET EXTERNAL PLUGIN
 
+## 1.9.13
+  - Add SET PLUGIN
+
 ## About
 
 This application describes the APDU messages interface to communicate with the Ethereum application. 
@@ -240,6 +243,49 @@ signed by the following secp256k1 public key 0482bbf2f34f367b2e5bc21847b6566f21f
 
 None
 
+### PROVIDE NFT INFORMATION
+
+#### Description
+
+This commands provides a trusted description of an NFT to associate a contract address with a collectionName.
+
+It shall be run immediately before performing a transaction involving a contract calling this contract address to display the proper nft information to the user if necessary, as marked in GET APP CONFIGURATION flags.
+
+The signature is computed on 
+
+type || version || len(collectionName) || collectionName || address || chainId || keyId || algorithmId || len(signature) || signature
+
+#### Coding
+
+'Command'
+
+[width="80%"]
+|==============================================================================================================================
+| *CLA* | *INS*  | *P1*               | *P2*       | *Lc*     | *Le*   
+|   E0  |   14   |  00   |   00       | variable | 00
+|==============================================================================================================================
+
+'Input data'
+
+[width="80%"]
+|==============================================================================================================================
+| *Description*                                                                     | *Length*
+| Type                                                                              | 1
+| Version                                                                           | 1
+| Collection Name Length                                                            | 1
+| Collection Name                                                                   | variable
+| Address                                                                           | 20
+| Chain ID                                                                          | 8
+| KeyID                                                                             | 1
+| Algorithm ID                                                                      | 1
+| Signature Length                                                                  | 1
+| Signature                                                                         | variable
+|==============================================================================================================================
+
+'Output data'
+
+None
+
 
 ### SET EXTERNAL PLUGIN
 
@@ -283,6 +329,56 @@ signed by the following secp256k1 public key 0482bbf2f34f367b2e5bc21847b6566f21f
 
 None
 
+### SET PLUGIN
+
+#### Description
+
+This commands provides the name of a trusted binding of a plugin with a contract address and a supported method selector. This plugin will be called to interpret contract data in the following transaction signing command.
+
+It can be used to set both internal and external plugins.
+
+It shall be run immediately before performing a transaction involving a contract supported by this plugin to display the proper information to the user if necessary.
+
+The function returns an error sw (0x6984) if the plugin requested is not installed on the device, 0x9000 otherwise.
+
+The plugin names `ERC20`, `ERC721` and `ERC1155` are reserved. Additional plugin names might be added to this list in the future.
+
+The signature is computed on
+
+type || version || len(pluginName) || pluginName || address || selector || chainId || keyId || algorithmId || len(signature) || signature
+
+#### Coding
+
+'Command'
+
+[width="80%"]
+|==============================================================================================================================
+| *CLA* | *INS*  | *P1*               | *P2*       | *Lc*     | *Le*
+|   E0  |   16   |  00   |   00       | variable   | 00
+|==============================================================================================================================
+
+'Input data'
+
+[width="80%"]
+|==============================================================================================================================
+| *Description*                                                                     | *Length*
+| Type                                                                              | 1
+| Version                                                                           | 1
+| Plugin Name Length                                                                | 1
+| Plugin Name                                                                       | variable
+| Address                                                                           | 20
+| Selector                                                                          | 4
+| Chain ID                                                                          | 8
+| KeyID                                                                             | 1
+| Algorithm ID                                                                      | 1
+| Signature Length                                                                  | 1
+| Signature                                                                         | variable
+|==============================================================================================================================
+
+'Output data'
+
+None
+
 ### GET APP CONFIGURATION
 
 #### Description
diff --git a/doc/ethapp_plugins.asc b/doc/ethapp_plugins.asc
index e1233fadb..af8908758 100644
--- a/doc/ethapp_plugins.asc
+++ b/doc/ethapp_plugins.asc
@@ -145,8 +145,8 @@ typedef struct ethPluginFinalize_t {
   ethPluginSharedRO_t *pluginSharedRO;
   uint8_t *pluginContext;
 
-  uint8_t *tokenLookup1; // set by the plugin if a token should be looked up
-  uint8_t *tokenLookup2; 
+  uint8_t *itemLookup1; // set by the plugin if a token or an nft should be looked up
+  uint8_t *itemLookup2; 
 
   uint8_t *amount; // set an uint256 pointer if uiType is UI_AMOUNT_ADDRESS
   uint8_t *address;  // set to the destination address if uiType is UI_AMOUNT_ADDRESS. Set to the user's address if uiType is UI_TYPE_GENERIC
@@ -161,8 +161,8 @@ typedef struct ethPluginFinalize_t {
 
 This message is sent when the data field has been fully parsed. The following specific fields can be filled by the plugin : 
 
-  * tokenLookup1 : the pointer shall be set to a 20 bytes address to look up an ERC 20 token descriptor if needed by the plugin
-  * tokenLookup2 : the pointer shall be set to a 20 bytes address to look up an ERC 20 token descriptor if needed by the plugin  
+  * itemLookup1 : the pointer shall be set to a 20 bytes address to look up an ERC20 token or NFT if needed by the plugin
+  * itemLookup2 : the pointer shall be set to a 20 bytes address to look up an ERC20 token or NFT if needed by the plugin  
   * uiType : set to either ETH_UI_TYPE_AMOUNT_ADDRESS for an amount/address UI or ETH_UI_TYPE_GENERIC for a generic UI
 
 The following specific fields are filled by the plugin when returning an amount/address UI :
@@ -179,32 +179,32 @@ The following return codes are expected, any other will abort the signing proces
   * ETH_PLUGIN_RESULT_OK : if the plugin can be successfully initialized
   * ETH_PLUGIN_RESULT_FALLBACK : if the signing logic should fallback to the generic one
 
-### ETH_PLUGIN_PROVIDE_TOKEN
+### ETH_PLUGIN_PROVIDE_INFO
 
 [source,C]
 ----
 
-typedef struct ethPluginProvideToken_t {
+typedef struct ethPluginProvideInfo_t {
 
   ethPluginSharedRW_t *pluginSharedRW;
   ethPluginSharedRO_t *pluginSharedRO;
   uint8_t *pluginContext;
 
-  tokenDefinition_t *token1; // set by the ETH application, to be saved by the plugin
-  tokenDefinition_t *token2;
+  union extraInfo *item1; // set by the ETH application, to be saved by the plugin
+  union extraInfo *item2;
 
-  uint8_t additionalScreens; // Used by the plugin if it needs to display additional screens based on the information received from the token definitions.
+  uint8_t additionalScreens; // Used by the plugin if it needs to display additional screens based on the information received.
 
   uint8_t result;
 
-} ethPluginProvideToken_t;
+} ethPluginProvideInfo_t;
 
 ----
 
-This message is sent if a token lookup was required by the plugin when parsing a finalize message. The following specific fields are filled when the plugin is called :
+This message is sent if an information lookup was required by the plugin when parsing a finalize message. The following specific fields are filled when the plugin is called :
 
-  * token1 : pointer to a token definition matching tokenLookup1, or NULL if not found 
-  * token2 : pointer to a token definition matching tokenLookup2, or NULL if not found or not requested
+  * item1 : pointer to an union matching itemLookup1, or NULL if not found 
+  * item2 : pointer to an union matching itemLookup2, or NULL if not found
 
 The following return codes are expected, any other will abort the signing process : 
 
@@ -233,7 +233,7 @@ typedef struct ethQueryContractID_t {
 
 ----
 
-This message is sent after the parsing finalization and token lookups if requested if a generic UI is used. The following specific fields are provided when the plugin is called :
+This message is sent after the parsing finalization and information lookups if requested if a generic UI is used. The following specific fields are provided when the plugin is called :
 
   * name : pointer to the name of the plugin, to be filled by the plugin
   * nameLength : maximum name length
@@ -253,6 +253,9 @@ typedef struct ethQueryContractUI_t {
 
   ethPluginSharedRW_t *pluginSharedRW;
   ethPluginSharedRO_t *pluginSharedRO;
+  union extraInfo_t *item1;
+  union extraInfo_t *item2;
+  char network_ticker[MAX_TICKER_LEN]; 
   uint8_t *pluginContext;
   uint8_t screenIndex;
   char *title;
@@ -268,6 +271,10 @@ typedef struct ethQueryContractUI_t {
 
 This message is sent when a plugin screen shall be displayed if a generic UI is used. The following specific fields are provided when the plugin is called : 
 
+
+  * item1 : pointer to token / nft information
+  * item2 : pointer to token / nft information
+  * network_ticker : string that holds the network ticker
   * screenIndex : index of the screen to display, starting from 0
   * title : pointer to the first line of the screen, to be filled by the plugin
   * titleLength : maximum title length
diff --git a/ethereum-plugin-sdk b/ethereum-plugin-sdk
index d672ec1d2..5ade2d77c 160000
--- a/ethereum-plugin-sdk
+++ b/ethereum-plugin-sdk
@@ -1,1 +1,1 @@
-Subproject commit d672ec1d2aadd3a272760168f58022ca674876e3
+Subproject commit 5ade2d77cb6f0f366210d014bc731212a22fc29c
diff --git a/src/apdu_constants.h b/src/apdu_constants.h
index fd3dff9fc..f0c149b7b 100644
--- a/src/apdu_constants.h
+++ b/src/apdu_constants.h
@@ -15,6 +15,8 @@
 #define INS_GET_ETH2_PUBLIC_KEY             0x0E
 #define INS_SET_ETH2_WITHDRAWAL_INDEX       0x10
 #define INS_SET_EXTERNAL_PLUGIN             0x12
+#define INS_PROVIDE_NFT_INFORMATION         0x14
+#define INS_SET_PLUGIN                      0x16
 #define P1_CONFIRM                          0x01
 #define P1_NON_CONFIRM                      0x00
 #define P2_NO_CHAINCODE                     0x00
@@ -64,6 +66,12 @@ void handleProvideErc20TokenInformation(uint8_t p1,
                                         uint16_t dataLength,
                                         unsigned int *flags,
                                         unsigned int *tx);
+void handleProvideNFTInformation(uint8_t p1,
+                                 uint8_t p2,
+                                 uint8_t *dataBuffer,
+                                 uint16_t dataLength,
+                                 unsigned int *flags,
+                                 unsigned int *tx);
 void handleSign(uint8_t p1,
                 uint8_t p2,
                 uint8_t *dataBuffer,
@@ -96,6 +104,13 @@ void handleSetExternalPlugin(uint8_t p1,
                              unsigned int *flags,
                              unsigned int *tx);
 
+void handleSetPlugin(uint8_t p1,
+                     uint8_t p2,
+                     uint8_t *workBuffer,
+                     uint16_t dataLength,
+                     unsigned int *flags,
+                     unsigned int *tx);
+
 #ifdef HAVE_ETH2
 
 void handleGetEth2PublicKey(uint8_t p1,
diff --git a/src/eth_plugin_handler.c b/src/eth_plugin_handler.c
index dac067a36..b82c6a57c 100644
--- a/src/eth_plugin_handler.c
+++ b/src/eth_plugin_handler.c
@@ -2,6 +2,7 @@
 #include "eth_plugin_handler.h"
 #include "eth_plugin_internal.h"
 #include "shared_context.h"
+#include "network.h"
 
 void eth_plugin_prepare_init(ethPluginInitContract_t *init, uint8_t *selector, uint32_t dataSize) {
     memset((uint8_t *) init, 0, sizeof(ethPluginInitContract_t));
@@ -21,8 +22,8 @@ void eth_plugin_prepare_finalize(ethPluginFinalize_t *finalize) {
     memset((uint8_t *) finalize, 0, sizeof(ethPluginFinalize_t));
 }
 
-void eth_plugin_prepare_provide_token(ethPluginProvideToken_t *provideToken) {
-    memset((uint8_t *) provideToken, 0, sizeof(ethPluginProvideToken_t));
+void eth_plugin_prepare_provide_info(ethPluginProvideInfo_t *provideToken) {
+    memset((uint8_t *) provideToken, 0, sizeof(ethPluginProvideInfo_t));
 }
 
 void eth_plugin_prepare_query_contract_ID(ethQueryContractID_t *queryContractID,
@@ -44,6 +45,23 @@ void eth_plugin_prepare_query_contract_UI(ethQueryContractUI_t *queryContractUI,
                                           char *msg,
                                           uint32_t msgLength) {
     memset((uint8_t *) queryContractUI, 0, sizeof(ethQueryContractUI_t));
+
+    // If no extra information was found, set the pointer to NULL
+    if (allzeroes(&tmpCtx.transactionContext.extraInfo[1], sizeof(union extraInfo_t))) {
+        queryContractUI->item1 = NULL;
+    } else {
+        queryContractUI->item1 = &tmpCtx.transactionContext.extraInfo[1];
+    }
+
+    // If no extra information was found, set the pointer to NULL
+    if (allzeroes(&tmpCtx.transactionContext.extraInfo[0], sizeof(union extraInfo_t))) {
+        queryContractUI->item2 = NULL;
+    } else {
+        queryContractUI->item2 = &tmpCtx.transactionContext.extraInfo[0];
+    }
+
+    strlcpy(queryContractUI->network_ticker, get_network_ticker(), MAX_TICKER_LEN);
+
     queryContractUI->screenIndex = screenIndex;
     strlcpy(queryContractUI->network_ticker,
             get_network_ticker(),
@@ -54,61 +72,83 @@ void eth_plugin_prepare_query_contract_UI(ethQueryContractUI_t *queryContractUI,
     queryContractUI->msgLength = msgLength;
 }
 
-eth_plugin_result_t eth_plugin_perform_init(uint8_t *contractAddress,
+static void eth_plugin_perform_init_default(uint8_t *contractAddress,
                                             ethPluginInitContract_t *init) {
-    uint8_t i;
+    // check if the registered external plugin matches the TX contract address / selector
+    if (memcmp(contractAddress,
+               dataContext.tokenContext.contractAddress,
+               sizeof(dataContext.tokenContext.contractAddress)) != 0) {
+        PRINTF("Got contract: %.*H\n", ADDRESS_LENGTH, contractAddress);
+        PRINTF("Expected contract: %.*H\n",
+               ADDRESS_LENGTH,
+               dataContext.tokenContext.contractAddress);
+        os_sched_exit(0);
+    }
+    if (memcmp(init->selector,
+               dataContext.tokenContext.methodSelector,
+               sizeof(dataContext.tokenContext.methodSelector)) != 0) {
+        PRINTF("Got selector: %.*H\n", SELECTOR_SIZE, init->selector);
+        PRINTF("Expected selector: %.*H\n", SELECTOR_SIZE, dataContext.tokenContext.methodSelector);
+        os_sched_exit(0);
+    }
+    PRINTF("Plugin will be used\n");
+    dataContext.tokenContext.pluginStatus = ETH_PLUGIN_RESULT_OK;
+}
+
+static bool eth_plugin_perform_init_old_internal(uint8_t *contractAddress,
+                                                 ethPluginInitContract_t *init) {
+    uint8_t i, j;
     const uint8_t **selectors;
-    dataContext.tokenContext.pluginStatus = ETH_PLUGIN_RESULT_UNAVAILABLE;
 
-    PRINTF("Selector %.*H\n", 4, init->selector);
-    if (externalPluginIsSet) {
-        // check if the registered external plugin matches the TX contract address / method selector
-        if (memcmp(contractAddress,
-                   dataContext.tokenContext.contract_address,
-                   sizeof(dataContext.tokenContext.contract_address)) != 0) {
-            PRINTF("Got contract: %.*H\n", ADDRESS_LENGTH, contractAddress);
-            PRINTF("Expected contract: %.*H\n",
-                   ADDRESS_LENGTH,
-                   dataContext.tokenContext.contract_address);
-            os_sched_exit(0);
-        }
-        if (memcmp(init->selector,
-                   dataContext.tokenContext.method_selector,
-                   sizeof(dataContext.tokenContext.method_selector)) != 0) {
-            PRINTF("Got selector: %.*H\n", SELECTOR_SIZE, init->selector);
-            PRINTF("Expected selector: %.*H\n",
-                   SELECTOR_SIZE,
-                   dataContext.tokenContext.method_selector);
-            os_sched_exit(0);
+    // Search internal plugin list
+    for (i = 0;; i++) {
+        selectors = (const uint8_t **) PIC(INTERNAL_ETH_PLUGINS[i].selectors);
+        if (selectors == NULL) {
+            break;
         }
-        PRINTF("External plugin will be used\n");
-        dataContext.tokenContext.pluginStatus = ETH_PLUGIN_RESULT_OK;
-        contractAddress = NULL;
-    } else {
-        // Search internal plugin list
-        for (i = 0;; i++) {
-            uint8_t j;
-            selectors = (const uint8_t **) PIC(INTERNAL_ETH_PLUGINS[i].selectors);
-            if (selectors == NULL) {
-                break;
-            }
-            for (j = 0; ((j < INTERNAL_ETH_PLUGINS[i].num_selectors) && (contractAddress != NULL));
-                 j++) {
-                if (memcmp(init->selector, (const void *) PIC(selectors[j]), SELECTOR_SIZE) == 0) {
-                    if ((INTERNAL_ETH_PLUGINS[i].availableCheck == NULL) ||
-                        ((PluginAvailableCheck) PIC(INTERNAL_ETH_PLUGINS[i].availableCheck))()) {
-                        strlcpy(dataContext.tokenContext.pluginName,
-                                INTERNAL_ETH_PLUGINS[i].alias,
-                                PLUGIN_ID_LENGTH);
-                        dataContext.tokenContext.pluginStatus = ETH_PLUGIN_RESULT_OK;
-                        contractAddress = NULL;
-                        break;
-                    }
+        for (j = 0; ((j < INTERNAL_ETH_PLUGINS[i].num_selectors) && (contractAddress != NULL));
+             j++) {
+            if (memcmp(init->selector, (const void *) PIC(selectors[j]), SELECTOR_SIZE) == 0) {
+                if ((INTERNAL_ETH_PLUGINS[i].availableCheck == NULL) ||
+                    ((PluginAvailableCheck) PIC(INTERNAL_ETH_PLUGINS[i].availableCheck))()) {
+                    strlcpy(dataContext.tokenContext.pluginName,
+                            INTERNAL_ETH_PLUGINS[i].alias,
+                            PLUGIN_ID_LENGTH);
+                    dataContext.tokenContext.pluginStatus = ETH_PLUGIN_RESULT_OK;
+                    return true;
                 }
             }
         }
     }
 
+    return false;
+}
+
+eth_plugin_result_t eth_plugin_perform_init(uint8_t *contractAddress,
+                                            ethPluginInitContract_t *init) {
+    dataContext.tokenContext.pluginStatus = ETH_PLUGIN_RESULT_UNAVAILABLE;
+
+    PRINTF("Selector %.*H\n", 4, init->selector);
+    switch (pluginType) {
+#ifdef HAVE_NFT_SUPPORT
+        case ERC1155:
+        case ERC721:
+#endif  // HAVE_NFT_SUPPORT
+        case EXTERNAL:
+            eth_plugin_perform_init_default(contractAddress, init);
+            contractAddress = NULL;
+            break;
+        case OLD_INTERNAL:
+            if (eth_plugin_perform_init_old_internal(contractAddress, init)) {
+                contractAddress = NULL;
+            }
+            break;
+        default:
+            PRINTF("Unsupported pluginType %d\n", pluginType);
+            os_sched_exit(0);
+            break;
+    }
+
     // Do not handle a plugin if running in swap mode
     if (called_from_swap && (contractAddress != NULL)) {
         PRINTF("eth_plug_init aborted in swap mode\n");
@@ -139,7 +179,6 @@ eth_plugin_result_t eth_plugin_call(int method, void *parameter) {
     ethPluginSharedRO_t pluginRO;
     char *alias;
     uint8_t i;
-    uint8_t internalPlugin = 0;
 
     pluginRW.sha3 = &global_sha3;
     pluginRO.txContent = &tmpContent.txContent;
@@ -182,12 +221,12 @@ eth_plugin_result_t eth_plugin_call(int method, void *parameter) {
             ((ethPluginFinalize_t *) parameter)->pluginContext =
                 (uint8_t *) &dataContext.tokenContext.pluginContext;
             break;
-        case ETH_PLUGIN_PROVIDE_TOKEN:
-            PRINTF("-- PLUGIN PROVIDE TOKEN --\n");
-            ((ethPluginProvideToken_t *) parameter)->result = ETH_PLUGIN_RESULT_UNAVAILABLE;
-            ((ethPluginProvideToken_t *) parameter)->pluginSharedRW = &pluginRW;
-            ((ethPluginProvideToken_t *) parameter)->pluginSharedRO = &pluginRO;
-            ((ethPluginProvideToken_t *) parameter)->pluginContext =
+        case ETH_PLUGIN_PROVIDE_INFO:
+            PRINTF("-- PLUGIN PROVIDE INFO --\n");
+            ((ethPluginProvideInfo_t *) parameter)->result = ETH_PLUGIN_RESULT_UNAVAILABLE;
+            ((ethPluginProvideInfo_t *) parameter)->pluginSharedRW = &pluginRW;
+            ((ethPluginProvideInfo_t *) parameter)->pluginSharedRO = &pluginRO;
+            ((ethPluginProvideInfo_t *) parameter)->pluginContext =
                 (uint8_t *) &dataContext.tokenContext.pluginContext;
             break;
         case ETH_PLUGIN_QUERY_CONTRACT_ID:
@@ -210,35 +249,52 @@ eth_plugin_result_t eth_plugin_call(int method, void *parameter) {
             return ETH_PLUGIN_RESULT_UNAVAILABLE;
     }
 
-    // Perform the call
-
-    for (i = 0;; i++) {
-        if (INTERNAL_ETH_PLUGINS[i].alias[0] == 0) {
+    switch (pluginType) {
+        case EXTERNAL: {
+            uint32_t params[3];
+            params[0] = (uint32_t) alias;
+            params[1] = method;
+            params[2] = (uint32_t) parameter;
+            BEGIN_TRY {
+                TRY {
+                    os_lib_call(params);
+                }
+                CATCH_OTHER(e) {
+                    PRINTF("Plugin call exception for %s\n", alias);
+                }
+                FINALLY {
+                }
+            }
+            END_TRY;
             break;
         }
-        if (strcmp(alias, INTERNAL_ETH_PLUGINS[i].alias) == 0) {
-            internalPlugin = 1;
-            ((PluginCall) PIC(INTERNAL_ETH_PLUGINS[i].impl))(method, parameter);
+#ifdef HAVE_NFT_SUPPORT
+        case ERC721: {
+            erc721_plugin_call(method, parameter);
             break;
         }
-    }
-
-    if (!internalPlugin) {
-        uint32_t params[3];
-        params[0] = (uint32_t) alias;
-        params[1] = method;
-        params[2] = (uint32_t) parameter;
-        BEGIN_TRY {
-            TRY {
-                os_lib_call(params);
-            }
-            CATCH_OTHER(e) {
-                PRINTF("Plugin call exception for %s\n", alias);
-            }
-            FINALLY {
+        case ERC1155: {
+            erc1155_plugin_call(method, parameter);
+            break;
+        }
+#endif  // HAVE_NFT_SUPPORT
+        case OLD_INTERNAL: {
+            // Perform the call
+            for (i = 0;; i++) {
+                if (INTERNAL_ETH_PLUGINS[i].alias[0] == 0) {
+                    break;
+                }
+                if (strcmp(alias, INTERNAL_ETH_PLUGINS[i].alias) == 0) {
+                    ((PluginCall) PIC(INTERNAL_ETH_PLUGINS[i].impl))(method, parameter);
+                    break;
+                }
             }
+            break;
+        }
+        default: {
+            PRINTF("Error with pluginType: %d\n", pluginType);
+            return ETH_PLUGIN_RESULT_ERROR;
         }
-        END_TRY;
     }
 
     // Check the call result
@@ -276,9 +332,9 @@ eth_plugin_result_t eth_plugin_call(int method, void *parameter) {
                     return ETH_PLUGIN_RESULT_UNAVAILABLE;
             }
             break;
-        case ETH_PLUGIN_PROVIDE_TOKEN:
-            PRINTF("RESULT: %d\n", ((ethPluginProvideToken_t *) parameter)->result);
-            switch (((ethPluginProvideToken_t *) parameter)->result) {
+        case ETH_PLUGIN_PROVIDE_INFO:
+            PRINTF("RESULT: %d\n", ((ethPluginProvideInfo_t *) parameter)->result);
+            switch (((ethPluginProvideInfo_t *) parameter)->result) {
                 case ETH_PLUGIN_RESULT_OK:
                 case ETH_PLUGIN_RESULT_FALLBACK:
                     break;
diff --git a/src/eth_plugin_handler.h b/src/eth_plugin_handler.h
index 8c86af3f3..864c15c60 100644
--- a/src/eth_plugin_handler.h
+++ b/src/eth_plugin_handler.h
@@ -7,7 +7,7 @@ void eth_plugin_prepare_provide_parameter(ethPluginProvideParameter_t *providePa
                                           uint8_t *parameter,
                                           uint32_t parameterOffset);
 void eth_plugin_prepare_finalize(ethPluginFinalize_t *finalize);
-void eth_plugin_prepare_provide_token(ethPluginProvideToken_t *provideToken);
+void eth_plugin_prepare_provide_info(ethPluginProvideInfo_t *provideToken);
 void eth_plugin_prepare_query_contract_ID(ethQueryContractID_t *queryContractID,
                                           char *name,
                                           uint32_t nameLength,
@@ -24,7 +24,6 @@ eth_plugin_result_t eth_plugin_perform_init(uint8_t *contractAddress,
                                             ethPluginInitContract_t *init);
 // NULL for cached address, or base contract address
 eth_plugin_result_t eth_plugin_call(int method, void *parameter);
-int compound_plugin_call(uint8_t *contractAddress, int method, void *parameter);
 
 void plugin_ui_start(void);
 
diff --git a/src/eth_plugin_interface.h b/src/eth_plugin_interface.h
index 188715418..512716ea2 100644
--- a/src/eth_plugin_interface.h
+++ b/src/eth_plugin_interface.h
@@ -6,14 +6,14 @@
 #include "cx.h"
 #include "ethUstream.h"
 #include "tokens.h"
-
-#define PLUGIN_ID_LENGTH 30
+#include "shared_context.h"
 
 // Interface version. To be updated everytime we introduce breaking changes to the plugin interface.
 typedef enum {
-    ETH_PLUGIN_INTERFACE_VERSION_1 = 1,  // Version 1
+    ETH_PLUGIN_INTERFACE_VERSION_1 = 1,
     ETH_PLUGIN_INTERFACE_VERSION_2 = 2,
-    ETH_PLUGIN_INTERFACE_VERSION_LATEST = 3,
+    ETH_PLUGIN_INTERFACE_VERSION_3 = 3,
+    ETH_PLUGIN_INTERFACE_VERSION_LATEST = 4,
 } eth_plugin_interface_version_t;
 
 typedef enum {
@@ -21,7 +21,7 @@ typedef enum {
     ETH_PLUGIN_INIT_CONTRACT = 0x0101,
     ETH_PLUGIN_PROVIDE_PARAMETER = 0x0102,
     ETH_PLUGIN_FINALIZE = 0x0103,
-    ETH_PLUGIN_PROVIDE_TOKEN = 0x0104,
+    ETH_PLUGIN_PROVIDE_INFO = 0x0104,
     ETH_PLUGIN_QUERY_CONTRACT_ID = 0x0105,
     ETH_PLUGIN_QUERY_CONTRACT_UI = 0x0106,
     ETH_PLUGIN_CHECK_PRESENCE = 0x01FF
@@ -126,20 +126,20 @@ typedef struct ethPluginFinalize_t {
 
 // Provide token
 
-typedef struct ethPluginProvideToken_t {
+typedef struct ethPluginProvideInfo_t {
     ethPluginSharedRW_t *pluginSharedRW;
     ethPluginSharedRO_t *pluginSharedRO;
     uint8_t *pluginContext;
 
-    tokenDefinition_t *token1;  // set by the ETH application, to be saved by the plugin
-    tokenDefinition_t *token2;
+    union extraInfo_t *item1;  // set by the ETH application, to be saved by the plugin
+    union extraInfo_t *item2;
 
     uint8_t additionalScreens;  // Used by the plugin if it needs to display additional screens
                                 // based on the information received from the token definitions.
 
     uint8_t result;
 
-} ethPluginProvideToken_t;
+} ethPluginProvideInfo_t;
 
 // Query Contract name and version
 
@@ -164,9 +164,11 @@ typedef struct ethQueryContractID_t {
 typedef struct ethQueryContractUI_t {
     ethPluginSharedRW_t *pluginSharedRW;
     ethPluginSharedRO_t *pluginSharedRO;
+    union extraInfo_t *item1;
+    union extraInfo_t *item2;
+    char network_ticker[MAX_TICKER_LEN];
     uint8_t *pluginContext;
     uint8_t screenIndex;
-    char network_ticker[MAX_TICKER_LEN];
 
     char *title;
     size_t titleLength;
diff --git a/src/eth_plugin_internal.c b/src/eth_plugin_internal.c
index 1ed45cd9d..a4789f1a7 100644
--- a/src/eth_plugin_internal.c
+++ b/src/eth_plugin_internal.c
@@ -1,11 +1,20 @@
 #include "eth_plugin_internal.h"
 
 bool erc20_plugin_available_check(void);
-bool erc721_plugin_available_check(void);
 
 void erc20_plugin_call(int message, void* parameters);
-void erc721_plugin_call(int message, void* parameters);
 void compound_plugin_call(int message, void* parameters);
+
+void copy_address(uint8_t* dst, uint8_t* parameter, uint8_t dst_size) {
+    uint8_t copy_size = MIN(dst_size, ADDRESS_LENGTH);
+    memmove(dst, parameter + PARAMETER_LENGTH - copy_size, copy_size);
+}
+
+void copy_parameter(uint8_t* dst, uint8_t* parameter, uint8_t dst_size) {
+    uint8_t copy_size = MIN(dst_size, PARAMETER_LENGTH);
+    memmove(dst, parameter, copy_size);
+}
+
 #ifdef HAVE_STARKWARE
 void starkware_plugin_call(int message, void* parameters);
 #endif
@@ -19,10 +28,6 @@ static const uint8_t ERC20_APPROVE_SELECTOR[SELECTOR_SIZE] = {0x09, 0x5e, 0xa7,
 const uint8_t* const ERC20_SELECTORS[NUM_ERC20_SELECTORS] = {ERC20_TRANSFER_SELECTOR,
                                                              ERC20_APPROVE_SELECTOR};
 
-static const uint8_t ERC721_APPROVE_SELECTOR[SELECTOR_SIZE] = {0x09, 0x5e, 0xa7, 0xb3};
-
-const uint8_t* const ERC721_SELECTORS[NUM_ERC721_SELECTORS] = {ERC721_APPROVE_SELECTOR};
-
 static const uint8_t COMPOUND_REDEEM_UNDERLYING_SELECTOR[SELECTOR_SIZE] = {0x85, 0x2a, 0x12, 0xe3};
 static const uint8_t COMPOUND_REDEEM_SELECTOR[SELECTOR_SIZE] = {0xdb, 0x00, 0x6a, 0x75};
 static const uint8_t COMPOUND_MINT_SELECTOR[SELECTOR_SIZE] = {0xa0, 0x71, 0x2d, 0x68};
@@ -105,12 +110,6 @@ const internalEthPlugin_t INTERNAL_ETH_PLUGINS[] = {
      "-erc20",
      erc20_plugin_call},
 
-    {erc721_plugin_available_check,
-     (const uint8_t**) ERC721_SELECTORS,
-     NUM_ERC721_SELECTORS,
-     "-er721",
-     erc721_plugin_call},
-
     {NULL,
      (const uint8_t**) COMPOUND_SELECTORS,
      NUM_COMPOUND_SELECTORS,
diff --git a/src/eth_plugin_internal.h b/src/eth_plugin_internal.h
index abce9c63e..4f10ec548 100644
--- a/src/eth_plugin_internal.h
+++ b/src/eth_plugin_internal.h
@@ -6,6 +6,13 @@
 #define PARAMETER_LENGTH 32
 #define RUN_APPLICATION  1
 
+void copy_address(uint8_t* dst, uint8_t* parameter, uint8_t dst_size);
+
+void copy_parameter(uint8_t* dst, uint8_t* parameter, uint8_t dst_size);
+
+void erc721_plugin_call(int message, void* parameters);
+void erc1155_plugin_call(int message, void* parameters);
+
 typedef bool (*PluginAvailableCheck)(void);
 
 typedef struct internalEthPlugin_t {
@@ -19,9 +26,6 @@ typedef struct internalEthPlugin_t {
 #define NUM_ERC20_SELECTORS 2
 extern const uint8_t* const ERC20_SELECTORS[NUM_ERC20_SELECTORS];
 
-#define NUM_ERC721_SELECTORS 1
-extern const uint8_t* const ERC721_SELECTORS[NUM_ERC721_SELECTORS];
-
 #define NUM_COMPOUND_SELECTORS 4
 extern const uint8_t* const COMPOUND_SELECTORS[NUM_COMPOUND_SELECTORS];
 
diff --git a/src/main.c b/src/main.c
index 999d1d981..a06bac04d 100644
--- a/src/main.c
+++ b/src/main.c
@@ -50,7 +50,7 @@ cx_sha3_t global_sha3;
 
 uint8_t appState;
 bool called_from_swap;
-bool externalPluginIsSet;
+pluginType_t pluginType;
 #ifdef HAVE_STARKWARE
 bool quantumSet;
 #endif
@@ -72,7 +72,7 @@ void reset_app_context() {
     // PRINTF("!!RESET_APP_CONTEXT\n");
     appState = APP_STATE_IDLE;
     called_from_swap = false;
-    externalPluginIsSet = false;
+    pluginType = OLD_INTERNAL;
 #ifdef HAVE_STARKWARE
     quantumSet = false;
 #endif
@@ -155,8 +155,8 @@ unsigned short io_exchange_al(unsigned char channel, unsigned short tx_len) {
     return 0;
 }
 
-tokenDefinition_t *getKnownToken(uint8_t *contractAddress) {
-    tokenDefinition_t *currentToken = NULL;
+extraInfo_t *getKnownToken(uint8_t *contractAddress) {
+    union extraInfo_t *currentItem = NULL;
 #ifdef HAVE_TOKENS_LIST
     uint32_t numTokens = 0;
     uint32_t i;
@@ -386,12 +386,22 @@ tokenDefinition_t *getKnownToken(uint8_t *contractAddress) {
         }
     }
 #endif
-    for (size_t i = 0; i < MAX_TOKEN; i++) {
-        currentToken = &tmpCtx.transactionContext.tokens[i];
+    //
+    for (uint8_t i = 0; i < MAX_ITEMS; i++) {
+        currentItem = (union extraInfo_t *) &tmpCtx.transactionContext.extraInfo[i].token;
         if (tmpCtx.transactionContext.tokenSet[i] &&
-            (memcmp(currentToken->address, contractAddress, ADDRESS_LENGTH) == 0)) {
+            (memcmp(currentItem->token.address, contractAddress, ADDRESS_LENGTH) == 0)) {
             PRINTF("Token found at index %d\n", i);
-            return currentToken;
+            return currentItem;
+        }
+    }
+
+    for (uint8_t i = 0; i < MAX_ITEMS; i++) {
+        currentItem = (union extraInfo_t *) &tmpCtx.transactionContext.extraInfo[i].token;
+        if (tmpCtx.transactionContext.tokenSet[i] &&
+            (memcmp(currentItem->nft.contractAddress, contractAddress, ADDRESS_LENGTH) == 0)) {
+            PRINTF("Token found at index %d\n", i);
+            return currentItem;
         }
     }
 
@@ -417,7 +427,7 @@ void handleGetWalletId(volatile unsigned int *tx) {
     // pubkey -> sha512
     cx_hash_sha512(pub.W, sizeof(pub.W), t, sizeof(t));
     // ! cookie !
-    os_memmove(G_io_apdu_buffer, t, 64);
+    memmove(G_io_apdu_buffer, t, 64);
     *tx = 64;
     THROW(0x9000);
 }
@@ -491,7 +501,7 @@ void handleApdu(unsigned int *flags, unsigned int *tx) {
 
             switch (G_io_apdu_buffer[OFFSET_INS]) {
                 case INS_GET_PUBLIC_KEY:
-                    memset(tmpCtx.transactionContext.tokenSet, 0, MAX_TOKEN);
+                    memset(tmpCtx.transactionContext.tokenSet, 0, MAX_ITEMS);
                     handleGetPublicKey(G_io_apdu_buffer[OFFSET_P1],
                                        G_io_apdu_buffer[OFFSET_P2],
                                        G_io_apdu_buffer + OFFSET_CDATA,
@@ -509,6 +519,17 @@ void handleApdu(unsigned int *flags, unsigned int *tx) {
                                                        tx);
                     break;
 
+#ifdef HAVE_NFT_SUPPORT
+                case INS_PROVIDE_NFT_INFORMATION:
+                    handleProvideNFTInformation(G_io_apdu_buffer[OFFSET_P1],
+                                                G_io_apdu_buffer[OFFSET_P2],
+                                                G_io_apdu_buffer + OFFSET_CDATA,
+                                                G_io_apdu_buffer[OFFSET_LC],
+                                                flags,
+                                                tx);
+                    break;
+#endif  // HAVE_NFT_SUPPORT
+
                 case INS_SET_EXTERNAL_PLUGIN:
                     handleSetExternalPlugin(G_io_apdu_buffer[OFFSET_P1],
                                             G_io_apdu_buffer[OFFSET_P2],
@@ -518,6 +539,15 @@ void handleApdu(unsigned int *flags, unsigned int *tx) {
                                             tx);
                     break;
 
+                case INS_SET_PLUGIN:
+                    handleSetPlugin(G_io_apdu_buffer[OFFSET_P1],
+                                    G_io_apdu_buffer[OFFSET_P2],
+                                    G_io_apdu_buffer + OFFSET_CDATA,
+                                    G_io_apdu_buffer[OFFSET_LC],
+                                    flags,
+                                    tx);
+                    break;
+
                 case INS_SIGN:
                     handleSign(G_io_apdu_buffer[OFFSET_P1],
                                G_io_apdu_buffer[OFFSET_P2],
@@ -537,7 +567,7 @@ void handleApdu(unsigned int *flags, unsigned int *tx) {
                     break;
 
                 case INS_SIGN_PERSONAL_MESSAGE:
-                    memset(tmpCtx.transactionContext.tokenSet, 0, MAX_TOKEN);
+                    memset(tmpCtx.transactionContext.tokenSet, 0, MAX_ITEMS);
                     handleSignPersonalMessage(G_io_apdu_buffer[OFFSET_P1],
                                               G_io_apdu_buffer[OFFSET_P2],
                                               G_io_apdu_buffer + OFFSET_CDATA,
@@ -547,7 +577,7 @@ void handleApdu(unsigned int *flags, unsigned int *tx) {
                     break;
 
                 case INS_SIGN_EIP_712_MESSAGE:
-                    memset(tmpCtx.transactionContext.tokenSet, 0, MAX_TOKEN);
+                    memset(tmpCtx.transactionContext.tokenSet, 0, MAX_ITEMS);
                     handleSignEIP712Message(G_io_apdu_buffer[OFFSET_P1],
                                             G_io_apdu_buffer[OFFSET_P2],
                                             G_io_apdu_buffer + OFFSET_CDATA,
@@ -559,7 +589,7 @@ void handleApdu(unsigned int *flags, unsigned int *tx) {
 #ifdef HAVE_ETH2
 
                 case INS_GET_ETH2_PUBLIC_KEY:
-                    memset(tmpCtx.transactionContext.tokenSet, 0, MAX_TOKEN);
+                    memset(tmpCtx.transactionContext.tokenSet, 0, MAX_ITEMS);
                     handleGetEth2PublicKey(G_io_apdu_buffer[OFFSET_P1],
                                            G_io_apdu_buffer[OFFSET_P2],
                                            G_io_apdu_buffer + OFFSET_CDATA,
@@ -768,7 +798,7 @@ void coin_main(chain_config_t *coin_config) {
         chainConfig = coin_config;
     }
     reset_app_context();
-    tmpCtx.transactionContext.currentTokenIndex = 0;
+    tmpCtx.transactionContext.currentItemIndex = 0;
 
     for (;;) {
         UX_INIT();
diff --git a/src/nft.h b/src/nft.h
new file mode 100644
index 000000000..f6b2d6774
--- /dev/null
+++ b/src/nft.h
@@ -0,0 +1,14 @@
+#include "tokens.h"
+
+// An `nftInfo_t` must be the same size as a `tokenDefinition_t`. This is because both will be held
+// in a `extraInfo_t` which is a union of a `nftInfo_t` and a `tokenDefinition_t`. By having both
+// struct the same size, we know they will be aligned, which facilitates accessing the items.
+
+// We defined the collection name max length to be the size of a `tokenDefinition_t` and remove the
+// `ADDRESS_LENGTH` which corresponds to `sizeof(contractAddress`).
+#define COLLECTION_NAME_MAX_LEN sizeof(tokenDefinition_t) - ADDRESS_LENGTH
+
+typedef struct nftInfo_t {
+    char collectionName[COLLECTION_NAME_MAX_LEN];
+    char contractAddress[ADDRESS_LENGTH];
+} nftInfo_t;
\ No newline at end of file
diff --git a/src/shared_context.h b/src/shared_context.h
index 09aa7d4e2..035f7c26f 100644
--- a/src/shared_context.h
+++ b/src/shared_context.h
@@ -14,16 +14,16 @@
 #include "uint256.h"
 #include "tokens.h"
 #include "chainConfig.h"
-#include "eth_plugin_interface.h"
+#include "nft.h"
 
 #define MAX_BIP32_PATH 10
 
-#define MAX_TOKEN 2
-
 #define WEI_TO_ETHER 18
 
 #define SELECTOR_LENGTH 4
 
+#define PLUGIN_ID_LENGTH 30
+
 #define N_storage (*(volatile internalStorage_t *) PIC(&N_storage_real))
 
 typedef struct internalStorage_t {
@@ -62,8 +62,8 @@ typedef struct tokenContext_t {
 
     union {
         struct {
-            uint8_t contract_address[ADDRESS_LENGTH];
-            uint8_t method_selector[SELECTOR_LENGTH];
+            uint8_t contractAddress[ADDRESS_LENGTH];
+            uint8_t methodSelector[SELECTOR_LENGTH];
         };
         uint8_t pluginContext[5 * INT256_LENGTH];
     };
@@ -84,13 +84,18 @@ typedef struct publicKeyContext_t {
     bool getChaincode;
 } publicKeyContext_t;
 
+typedef union extraInfo_t {
+    tokenDefinition_t token;
+    nftInfo_t nft;
+} extraInfo_t;
+
 typedef struct transactionContext_t {
     uint8_t pathLength;
     uint32_t bip32Path[MAX_BIP32_PATH];
     uint8_t hash[INT256_LENGTH];
-    tokenDefinition_t tokens[MAX_TOKEN];
-    uint8_t tokenSet[MAX_TOKEN];
-    uint8_t currentTokenIndex;
+    union extraInfo_t extraInfo[MAX_ITEMS];
+    uint8_t tokenSet[MAX_ITEMS];
+    uint8_t currentItemIndex;
 } transactionContext_t;
 
 typedef struct messageSigningContext_t {
@@ -137,6 +142,7 @@ typedef struct starkContext_t {
 
 typedef union {
     tokenContext_t tokenContext;
+
 #ifdef HAVE_STARKWARE
     starkContext_t starkContext;
 #endif
@@ -166,7 +172,7 @@ typedef enum {
 
 typedef struct txStringProperties_t {
     char fullAddress[43];
-    char fullAmount[67];
+    char fullAmount[79];  // 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];
@@ -196,7 +202,16 @@ extern cx_sha3_t global_sha3;
 extern const internalStorage_t N_storage_real;
 
 extern bool called_from_swap;
-extern bool externalPluginIsSet;
+
+typedef enum {
+    EXTERNAL,     //  External plugin, set by setExternalPlugin.
+    ERC721,       // Specific ERC721 internal plugin, set by setPlugin.
+    ERC1155,      // Specific ERC1155 internal plugin, set by setPlugin
+    OLD_INTERNAL  // Old internal plugin, not set by any command.
+} pluginType_t;
+
+extern pluginType_t pluginType;
+
 extern uint8_t appState;
 #ifdef HAVE_STARKWARE
 extern bool quantumSet;
diff --git a/src/stark_crypto.c b/src/stark_crypto.c
index dc4faceea..990216a07 100644
--- a/src/stark_crypto.c
+++ b/src/stark_crypto.c
@@ -68,7 +68,7 @@ void stark_get_amount_string(uint8_t *contractAddress,
         decimals = WEI_TO_ETHER;
         PRINTF("stark_get_amount_string - ETH\n");
     } else {
-        tokenDefinition_t *token = getKnownToken(contractAddress);
+        tokenDefinition_t *token = &getKnownToken(contractAddress)->token;
         if (token == NULL) {  // caught earlier
             THROW(0x6A80);
         }
diff --git a/src/tokens.h b/src/tokens.h
index 4c3b2eeb6..4391d67f3 100644
--- a/src/tokens.h
+++ b/src/tokens.h
@@ -22,6 +22,7 @@
 #include "ethUstream.h"
 
 #define MAX_TICKER_LEN 12  // 10 characters + ' ' + '\0'
+#define MAX_ITEMS      2
 
 typedef struct tokenDefinition_t {
 #ifdef HAVE_CONTRACT_NAME_IN_DESCRIPTOR
@@ -29,6 +30,9 @@ typedef struct tokenDefinition_t {
 #endif
     uint8_t address[ADDRESS_LENGTH];
     char ticker[MAX_TICKER_LEN];
+    char nft_pad[20];  // Adding some padding because the `nftInfo_t` is based on the size of a
+                       // `tokenDefinition_t`. By adding some padding here we give more space to the
+                       // collection name in the `nftInfo_t`. See `nftInfo_t` for more information.
     uint8_t decimals;
 } tokenDefinition_t;
 
diff --git a/src/ui_callbacks.h b/src/ui_callbacks.h
index 8b0a15ab4..e7e78ab08 100644
--- a/src/ui_callbacks.h
+++ b/src/ui_callbacks.h
@@ -21,4 +21,4 @@ void ui_warning_contract_data(void);
 void io_seproxyhal_send_status(uint32_t sw);
 void format_signature_out(const uint8_t *signature);
 void finalizeParsing(bool direct);
-tokenDefinition_t *getKnownToken(uint8_t *contractAddress);
+extraInfo_t *getKnownToken(uint8_t *contractAddress);
diff --git a/src/ui_flow.c b/src/ui_flow.c
index b6b2fa369..d6c0a1672 100644
--- a/src/ui_flow.c
+++ b/src/ui_flow.c
@@ -171,7 +171,7 @@ UX_STEP_CB(
       "Error",
       "Blind signing must be enabled in Settings",
     });
-#elif defined(TARGET_NANOX)
+#elif defined(TARGET_NANOX) || defined(TARGET_NANOS2)
 UX_STEP_CB(
     ux_warning_contract_data_step,
     pnn,
diff --git a/src_common/ethUstream.c b/src_common/ethUstream.c
index a706e6e01..7f6bc658f 100644
--- a/src_common/ethUstream.c
+++ b/src_common/ethUstream.c
@@ -254,6 +254,7 @@ static void processTo(txContext_t *context) {
 }
 
 static void processData(txContext_t *context) {
+    PRINTF("PROCESS DATA\n");
     if (context->currentFieldIsList) {
         PRINTF("Invalid type for RLP_DATA\n");
         THROW(EXCEPTION);
@@ -268,6 +269,7 @@ static void processData(txContext_t *context) {
         copyTxData(context, NULL, copySize);
     }
     if (context->currentFieldPos == context->currentFieldLength) {
+        PRINTF("incrementing field\n");
         context->currentField++;
         context->processingField = false;
     }
@@ -506,6 +508,7 @@ static parserStatus_e processTxInternal(txContext_t *context) {
         customStatus_e customStatus = CUSTOM_NOT_HANDLED;
         // EIP 155 style transaction
         if (PARSING_IS_DONE(context)) {
+            PRINTF("parsing is done\n");
             return USTREAM_FINISHED;
         }
         // Old style transaction (pre EIP-155). Transations could just skip `v,r,s` so we needed to
@@ -518,9 +521,11 @@ static parserStatus_e processTxInternal(txContext_t *context) {
         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;
         }
         if (!context->processingField) {
@@ -531,6 +536,7 @@ static parserStatus_e processTxInternal(txContext_t *context) {
         }
         if (context->customProcessor != NULL) {
             customStatus = context->customProcessor(context);
+            PRINTF("After customprocessor\n");
             switch (customStatus) {
                 case CUSTOM_NOT_HANDLED:
                 case CUSTOM_HANDLED:
@@ -571,11 +577,12 @@ static parserStatus_e processTxInternal(txContext_t *context) {
                         break;
                     }
                 default:
-                    PRINTF("Transaction type %u is not supported\n", context->txType);
+                    PRINTF("Transaction type %d is not supported\n", context->txType);
                     return USTREAM_FAULT;
             }
         }
     }
+    PRINTF("end of here\n");
 }
 
 parserStatus_e processTx(txContext_t *context,
@@ -589,6 +596,7 @@ parserStatus_e processTx(txContext_t *context,
             context->commandLength = length;
             context->processingFlags = processingFlags;
             result = processTxInternal(context);
+            PRINTF("result: %d\n");
         }
         CATCH_OTHER(e) {
             result = USTREAM_FAULT;
diff --git a/src_features/provideErc20TokenInformation/cmd_provideTokenInfo.c b/src_features/provideErc20TokenInformation/cmd_provideTokenInfo.c
index 94ddebca1..4f09b1fab 100644
--- a/src_features/provideErc20TokenInformation/cmd_provideTokenInfo.c
+++ b/src_features/provideErc20TokenInformation/cmd_provideTokenInfo.c
@@ -23,10 +23,10 @@ void handleProvideErc20TokenInformation(uint8_t p1,
 
     cx_sha256_init(&sha256);
 
-    tmpCtx.transactionContext.currentTokenIndex =
-        (tmpCtx.transactionContext.currentTokenIndex + 1) % MAX_TOKEN;
+    tmpCtx.transactionContext.currentItemIndex =
+        (tmpCtx.transactionContext.currentItemIndex + 1) % MAX_ITEMS;
     tokenDefinition_t *token =
-        &tmpCtx.transactionContext.tokens[tmpCtx.transactionContext.currentTokenIndex];
+        &tmpCtx.transactionContext.tokens[tmpCtx.transactionContext.currentItemIndex];
 
     if (dataLength < 1) {
         THROW(0x6A80);
@@ -93,7 +93,7 @@ void handleProvideErc20TokenInformation(uint8_t p1,
         THROW(0x6A80);
 #endif
     }
-    tmpCtx.transactionContext.tokenSet[tmpCtx.transactionContext.currentTokenIndex] = 1;
+    tmpCtx.transactionContext.tokenSet[tmpCtx.transactionContext.currentItemIndex] = 1;
     THROW(0x9000);
 }
 
@@ -114,12 +114,12 @@ void handleProvideErc20TokenInformation(uint8_t p1,
     uint8_t hash[INT256_LENGTH];
     cx_ecfp_public_key_t tokenKey;
 
-    tmpCtx.transactionContext.currentTokenIndex =
-        (tmpCtx.transactionContext.currentTokenIndex + 1) % MAX_TOKEN;
+    tmpCtx.transactionContext.currentItemIndex =
+        (tmpCtx.transactionContext.currentItemIndex + 1) % MAX_ITEMS;
     tokenDefinition_t *token =
-        &tmpCtx.transactionContext.tokens[tmpCtx.transactionContext.currentTokenIndex];
+        &tmpCtx.transactionContext.extraInfo[tmpCtx.transactionContext.currentItemIndex].token;
 
-    PRINTF("Provisioning currentTokenIndex %d\n", tmpCtx.transactionContext.currentTokenIndex);
+    PRINTF("Provisioning currentItemIndex %d\n", tmpCtx.transactionContext.currentItemIndex);
 
     if (dataLength < 1) {
         THROW(0x6A80);
@@ -204,7 +204,7 @@ void handleProvideErc20TokenInformation(uint8_t p1,
     }
 #endif
 
-    tmpCtx.transactionContext.tokenSet[tmpCtx.transactionContext.currentTokenIndex] = 1;
+    tmpCtx.transactionContext.tokenSet[tmpCtx.transactionContext.currentItemIndex] = 1;
     THROW(0x9000);
 }
 
diff --git a/src_features/provideNFTInformation/cmd_provideNFTInfo.c b/src_features/provideNFTInformation/cmd_provideNFTInfo.c
new file mode 100644
index 000000000..a4256a3d0
--- /dev/null
+++ b/src_features/provideNFTInformation/cmd_provideNFTInfo.c
@@ -0,0 +1,230 @@
+#ifdef HAVE_NFT_SUPPORT
+
+#include "shared_context.h"
+#include "apdu_constants.h"
+#include "ui_flow.h"
+#include "tokens.h"
+#include "utils.h"
+
+#define TYPE_SIZE        1
+#define VERSION_SIZE     1
+#define NAME_LENGTH_SIZE 1
+#define HEADER_SIZE      TYPE_SIZE + VERSION_SIZE + NAME_LENGTH_SIZE
+
+#define CHAIN_ID_SIZE         8
+#define KEY_ID_SIZE           1
+#define ALGORITHM_ID_SIZE     1
+#define SIGNATURE_LENGTH_SIZE 1
+#define MIN_DER_SIG_SIZE      67
+#define MAX_DER_SIG_SIZE      72
+
+#define TEST_NFT_METADATA_KEY 0
+#define PROD_NFT_METADATA_KEY 1
+
+#define ALGORITHM_ID_1 1
+
+#define TYPE_1 1
+
+#define VERSION_1 1
+
+static const uint8_t LEDGER_NFT_METADATA_PUBLIC_KEY[] = {
+#ifdef HAVE_NFT_TESTING_KEY
+    0x04, 0xf5, 0x70, 0x0c, 0xa1, 0xe8, 0x74, 0x24, 0xc7, 0xc7, 0xd1, 0x19, 0xe7, 0xe3,
+    0xc1, 0x89, 0xb1, 0x62, 0x50, 0x94, 0xdb, 0x6e, 0xa0, 0x40, 0x87, 0xc8, 0x30, 0x00,
+    0x7d, 0x0b, 0x46, 0x9a, 0x53, 0x11, 0xee, 0x6a, 0x1a, 0xcd, 0x1d, 0xa5, 0xaa, 0xb0,
+    0xf5, 0xc6, 0xdf, 0x13, 0x15, 0x8d, 0x28, 0xcc, 0x12, 0xd1, 0xdd, 0xa6, 0xec, 0xe9,
+    0x46, 0xb8, 0x9d, 0x5c, 0x05, 0x49, 0x92, 0x59, 0xc4
+
+#else
+    0x04, 0x98, 0x8d, 0xa6, 0xb2, 0x46, 0xf2, 0x8e, 0x77, 0xc1, 0xba, 0xb6, 0x75, 0xcb,
+    0x2a, 0x27, 0x44, 0xf7, 0xf5, 0xce, 0xc5, 0x6a, 0xe6, 0xe0, 0x32, 0x23, 0x33, 0x7b,
+    0x57, 0x94, 0xcd, 0x6a, 0xe0, 0x7d, 0x48, 0xb3, 0x0d, 0xb9, 0xcc, 0xb4, 0x0f, 0x5a,
+    0x02, 0xa1, 0x1a, 0x3a, 0xb9, 0x9d, 0x5f, 0x59, 0x5a, 0x3d, 0x50, 0xa0, 0xe1, 0x30,
+    0x23, 0xfd, 0x0d, 0x95, 0x87, 0x92, 0xd7, 0x97, 0x01
+#endif
+};
+
+typedef bool verificationAlgo(const cx_ecfp_public_key_t *,
+                              int,
+                              cx_md_t,
+                              const unsigned char *,
+                              unsigned int,
+                              unsigned char *,
+                              unsigned int);
+
+void handleProvideNFTInformation(uint8_t p1,
+                                 uint8_t p2,
+                                 uint8_t *workBuffer,
+                                 uint16_t dataLength,
+                                 unsigned int *flags,
+                                 unsigned int *tx) {
+    UNUSED(p1);
+    UNUSED(p2);
+    UNUSED(tx);
+    UNUSED(flags);
+    uint8_t hash[INT256_LENGTH];
+    cx_ecfp_public_key_t nftKey;
+    PRINTF("In handle provide NFTInformation");
+
+    tmpCtx.transactionContext.currentItemIndex =
+        (tmpCtx.transactionContext.currentItemIndex + 1) % MAX_ITEMS;
+    nftInfo_t *nft =
+        &tmpCtx.transactionContext.extraInfo[tmpCtx.transactionContext.currentItemIndex].nft;
+
+    PRINTF("Provisioning currentItemIndex %d\n", tmpCtx.transactionContext.currentItemIndex);
+
+    uint8_t offset = 0;
+
+    if (dataLength <= HEADER_SIZE) {
+        PRINTF("Data too small for headers: expected at least %d, got %d\n",
+               HEADER_SIZE,
+               dataLength);
+        THROW(0x6A80);
+    }
+
+    uint8_t type = workBuffer[offset];
+    switch (type) {
+        case TYPE_1:
+            break;
+        default:
+            PRINTF("Unsupported type %d\n", type);
+            THROW(0x6a80);
+            break;
+    }
+    offset += TYPE_SIZE;
+
+    uint8_t version = workBuffer[offset];
+    switch (version) {
+        case VERSION_1:
+            break;
+        default:
+            PRINTF("Unsupported version %d\n", version);
+            THROW(0x6a80);
+            break;
+    }
+    offset += VERSION_SIZE;
+
+    uint8_t collectionNameLength = workBuffer[offset];
+    offset += NAME_LENGTH_SIZE;
+
+    // Size of the payload (everything except the signature)
+    uint8_t payloadSize = HEADER_SIZE + collectionNameLength + ADDRESS_LENGTH + CHAIN_ID_SIZE +
+                          KEY_ID_SIZE + ALGORITHM_ID_SIZE;
+    if (dataLength < payloadSize) {
+        PRINTF("Data too small for payload: expected at least %d, got %d\n",
+               payloadSize,
+               dataLength);
+        THROW(0x6A80);
+    }
+
+    if (collectionNameLength + 1 > sizeof(nft->collectionName)) {
+        PRINTF("CollectionName too big: expected max %d, got %d\n",
+               sizeof(nft->collectionName),
+               collectionNameLength + 1);
+        THROW(0x6A80);
+    }
+
+    // Safe because we've checked the size before.
+    memcpy(nft->collectionName, workBuffer + offset, collectionNameLength);
+    nft->collectionName[collectionNameLength] = '\0';
+
+    PRINTF("Length: %d\n", collectionNameLength);
+    PRINTF("CollectionName: %s\n", nft->collectionName);
+    offset += collectionNameLength;
+
+    memcpy(nft->contractAddress, workBuffer + offset, ADDRESS_LENGTH);
+    PRINTF("Address: %.*H\n", ADDRESS_LENGTH, workBuffer + offset);
+    offset += ADDRESS_LENGTH;
+
+    uint64_t chainId = u64_from_BE(workBuffer + offset, CHAIN_ID_SIZE);
+    // this prints raw data, so to have a more meaningful print, display
+    // the buffer before the endianness swap
+    PRINTF("ChainID: %.*H\n", sizeof(chainId), (workBuffer + offset));
+    if ((chainConfig->chainId != 0) && (chainConfig->chainId != chainId)) {
+        PRINTF("Chain ID token mismatch\n");
+        THROW(0x6A80);
+    }
+    offset += CHAIN_ID_SIZE;
+
+    uint8_t keyId = workBuffer[offset];
+    uint8_t *rawKey;
+    uint8_t rawKeyLen;
+
+    PRINTF("KeyID: %d\n", keyId);
+    switch (keyId) {
+#ifdef HAVE_NFT_TESTING_KEY
+        case TEST_NFT_METADATA_KEY:
+#endif
+        case PROD_NFT_METADATA_KEY:
+            rawKey = (uint8_t *) LEDGER_NFT_METADATA_PUBLIC_KEY;
+            rawKeyLen = sizeof(LEDGER_NFT_METADATA_PUBLIC_KEY);
+            break;
+        default:
+            PRINTF("KeyID %d not supported\n", keyId);
+            THROW(0x6A80);
+            break;
+    }
+    PRINTF("RawKey: %.*H\n", rawKeyLen, rawKey);
+    offset += KEY_ID_SIZE;
+
+    uint8_t algorithmId = workBuffer[offset];
+    PRINTF("Algorithm: %d\n", algorithmId);
+    cx_curve_t curve;
+    verificationAlgo *verificationFn;
+    cx_md_t hashId;
+
+    switch (algorithmId) {
+        case ALGORITHM_ID_1:
+            curve = CX_CURVE_256K1;
+            verificationFn = (verificationAlgo *) cx_ecdsa_verify;
+            hashId = CX_SHA256;
+            break;
+        default:
+            PRINTF("Incorrect algorithmId %d\n", algorithmId);
+            THROW(0x6a80);
+            break;
+    }
+    offset += ALGORITHM_ID_SIZE;
+    PRINTF("hashing: %.*H\n", payloadSize, workBuffer);
+    cx_hash_sha256(workBuffer, payloadSize, hash, sizeof(hash));
+
+    if (dataLength < payloadSize + SIGNATURE_LENGTH_SIZE) {
+        PRINTF("Data too short to hold signature length\n");
+        THROW(0x6a80);
+    }
+
+    uint8_t signatureLen = workBuffer[offset];
+    PRINTF("Sigature len: %d\n", signatureLen);
+    if (signatureLen < MIN_DER_SIG_SIZE || signatureLen > MAX_DER_SIG_SIZE) {
+        PRINTF("SignatureLen too big or too small. Must be between %d and %d, got %d\n",
+               MIN_DER_SIG_SIZE,
+               MAX_DER_SIG_SIZE,
+               signatureLen);
+        THROW(0x6a80);
+    }
+    offset += SIGNATURE_LENGTH_SIZE;
+
+    if (dataLength < payloadSize + SIGNATURE_LENGTH_SIZE + signatureLen) {
+        PRINTF("Signature could not fit in data\n");
+        THROW(0x6a80);
+    }
+
+    cx_ecfp_init_public_key(curve, rawKey, rawKeyLen, &nftKey);
+    if (!verificationFn(&nftKey,
+                        CX_LAST,
+                        hashId,
+                        hash,
+                        sizeof(hash),
+                        workBuffer + offset,
+                        signatureLen)) {
+#ifndef HAVE_BYPASS_SIGNATURES
+        PRINTF("Invalid NFT signature\n");
+        THROW(0x6A80);
+#endif
+    }
+
+    tmpCtx.transactionContext.tokenSet[tmpCtx.transactionContext.currentItemIndex] = 1;
+    THROW(0x9000);
+}
+
+#endif  // HAVE_NFT_SUPPORT
diff --git a/src_features/setExternalPlugin/cmd_setExternalPlugin.c b/src_features/setExternalPlugin/cmd_setExternalPlugin.c
index 93c0b64b6..672bfaac6 100644
--- a/src_features/setExternalPlugin/cmd_setExternalPlugin.c
+++ b/src_features/setExternalPlugin/cmd_setExternalPlugin.c
@@ -2,8 +2,8 @@
 #include "apdu_constants.h"
 #include "ui_flow.h"
 #include "tokens.h"
-
-#define SELECTOR_SIZE 4
+#include "eth_plugin_interface.h"
+#include "eth_plugin_internal.h"
 
 void handleSetExternalPlugin(uint8_t p1,
                              uint8_t p2,
@@ -14,17 +14,22 @@ void handleSetExternalPlugin(uint8_t p1,
     UNUSED(p1);
     UNUSED(p2);
     UNUSED(flags);
-    PRINTF("Handling set External Plugin\n");
-    uint8_t hash[32];
+    PRINTF("Handling set Plugin\n");
+    uint8_t hash[INT256_LENGTH];
     cx_ecfp_public_key_t tokenKey;
     uint8_t pluginNameLength = *workBuffer;
+    PRINTF("plugin Name Length: %d\n", pluginNameLength);
     const size_t payload_size = 1 + pluginNameLength + ADDRESS_LENGTH + SELECTOR_SIZE;
 
     if (dataLength <= payload_size) {
+        PRINTF("data too small: expected at least %d got %d\n", payload_size, dataLength);
         THROW(0x6A80);
     }
 
     if (pluginNameLength + 1 > sizeof(dataContext.tokenContext.pluginName)) {
+        PRINTF("name length too big: expected max %d, got %d\n",
+               sizeof(dataContext.tokenContext.pluginName),
+               pluginNameLength + 1);
         THROW(0x6A80);
     }
 
@@ -41,8 +46,8 @@ void handleSetExternalPlugin(uint8_t p1,
                          sizeof(hash),
                          workBuffer + payload_size,
                          dataLength - payload_size)) {
-        PRINTF("Invalid external plugin signature %.*H\n", payload_size, workBuffer);
 #ifndef HAVE_BYPASS_SIGNATURES
+        PRINTF("Invalid plugin signature %.*H\n", payload_size, workBuffer);
         THROW(0x6A80);
 #endif
     }
@@ -77,10 +82,11 @@ void handleSetExternalPlugin(uint8_t p1,
 
     PRINTF("Plugin found\n");
 
-    memmove(dataContext.tokenContext.contract_address, workBuffer, ADDRESS_LENGTH);
+    memmove(dataContext.tokenContext.contractAddress, workBuffer, ADDRESS_LENGTH);
     workBuffer += ADDRESS_LENGTH;
-    memmove(dataContext.tokenContext.method_selector, workBuffer, SELECTOR_SIZE);
-    externalPluginIsSet = true;
+    memmove(dataContext.tokenContext.methodSelector, workBuffer, SELECTOR_SIZE);
+
+    pluginType = EXTERNAL;
 
     G_io_apdu_buffer[(*tx)++] = 0x90;
     G_io_apdu_buffer[(*tx)++] = 0x00;
diff --git a/src_features/setPlugin/cmd_setPlugin.c b/src_features/setPlugin/cmd_setPlugin.c
new file mode 100644
index 000000000..d2969f7bc
--- /dev/null
+++ b/src_features/setPlugin/cmd_setPlugin.c
@@ -0,0 +1,296 @@
+#include "shared_context.h"
+#include "apdu_constants.h"
+#include "ui_flow.h"
+#include "tokens.h"
+#include "eth_plugin_interface.h"
+#include "eth_plugin_internal.h"
+#include "utils.h"
+
+// Supported internal plugins
+#define ERC721_STR  "ERC721"
+#define ERC1155_STR "ERC1155"
+
+#define TYPE_SIZE               1
+#define VERSION_SIZE            1
+#define PLUGIN_NAME_LENGTH_SIZE 1
+#define CHAIN_ID_SIZE           8
+#define KEY_ID_SIZE             1
+#define ALGORITHM_ID_SIZE       1
+#define SIGNATURE_LENGTH_SIZE   1
+
+#define HEADER_SIZE TYPE_SIZE + VERSION_SIZE + PLUGIN_NAME_LENGTH_SIZE
+
+#define MIN_DER_SIG_SIZE 67
+#define MAX_DER_SIG_SIZE 72
+
+typedef enum Type {
+    ETH_PLUGIN = 0x01,
+} Type;
+
+typedef enum Version {
+    VERSION_1 = 0x01,
+} Version;
+
+typedef enum KeyId {
+    TEST_PLUGIN_KEY = 0x00,
+    // Must ONLY be used with ERC721 and ERC1155 plugin
+    PROD_PLUGIN_KEY = 0x02,
+} KeyId;
+
+// Algorithm Id consists of a Key spec and an algorithm spec.
+// Format is: KEYSPEC__ALGOSPEC
+typedef enum AlgorithmID {
+    ECC_SECG_P256K1__ECDSA_SHA_256 = 0x01,
+} AlgorithmID;
+
+// Only used for signing NFT plugins (ERC721 and ERC1155)
+static const uint8_t LEDGER_NFT_SELECTOR_PUBLIC_KEY[] = {
+#ifdef HAVE_NFT_TESTING_KEY
+    0x04, 0xf5, 0x70, 0x0c, 0xa1, 0xe8, 0x74, 0x24, 0xc7, 0xc7, 0xd1, 0x19, 0xe7, 0xe3,
+    0xc1, 0x89, 0xb1, 0x62, 0x50, 0x94, 0xdb, 0x6e, 0xa0, 0x40, 0x87, 0xc8, 0x30, 0x00,
+    0x7d, 0x0b, 0x46, 0x9a, 0x53, 0x11, 0xee, 0x6a, 0x1a, 0xcd, 0x1d, 0xa5, 0xaa, 0xb0,
+    0xf5, 0xc6, 0xdf, 0x13, 0x15, 0x8d, 0x28, 0xcc, 0x12, 0xd1, 0xdd, 0xa6, 0xec, 0xe9,
+    0x46, 0xb8, 0x9d, 0x5c, 0x05, 0x49, 0x92, 0x59, 0xc4
+#else
+    0x04, 0xd8, 0x62, 0x6e, 0x01, 0x9e, 0x55, 0x3e, 0x19, 0x69, 0x56, 0xf1, 0x17, 0x4d,
+    0xcd, 0xb8, 0x9a, 0x1c, 0xda, 0xc4, 0x93, 0x90, 0x08, 0xbc, 0x79, 0x77, 0x33, 0x6d,
+    0x78, 0x24, 0xee, 0xe3, 0xa2, 0x62, 0x24, 0x1a, 0x62, 0x73, 0x52, 0x3b, 0x09, 0xb8,
+    0xd0, 0xce, 0x0d, 0x39, 0xe8, 0x60, 0xc9, 0x4d, 0x02, 0x53, 0x58, 0xdb, 0xdc, 0x25,
+    0x92, 0xc7, 0xc6, 0x48, 0x0d, 0x39, 0xce, 0xbb, 0xa3
+#endif
+};
+
+// Verification function used to verify the signature
+typedef bool verificationAlgo(const cx_ecfp_public_key_t *,
+                              int,
+                              cx_md_t,
+                              const unsigned char *,
+                              unsigned int,
+                              unsigned char *,
+                              unsigned int);
+
+// Returns the plugin type of a given plugin name.
+// If the plugin name is not a specific known internal plugin, this function default return value is
+// `EXERNAL`.
+static pluginType_t getPluginType(char *pluginName, uint8_t pluginNameLength) {
+    if (pluginNameLength == sizeof(ERC721_STR) - 1 &&
+        strncmp(pluginName, ERC721_STR, pluginNameLength) == 0) {
+        return ERC721;
+    } else if (pluginNameLength == sizeof(ERC1155_STR) - 1 &&
+               strncmp(pluginName, ERC1155_STR, pluginNameLength) == 0) {
+        return ERC1155;
+    } else {
+        return EXTERNAL;
+    }
+}
+
+void handleSetPlugin(uint8_t p1,
+                     uint8_t p2,
+                     uint8_t *workBuffer,
+                     uint16_t dataLength,
+                     unsigned int *flags,
+                     unsigned int *tx) {
+    UNUSED(p1);
+    UNUSED(p2);
+    UNUSED(flags);
+    PRINTF("Handling set Plugin\n");
+    uint8_t hash[INT256_LENGTH] = {0};
+    cx_ecfp_public_key_t pluginKey = {0};
+    tokenContext_t *tokenContext = &dataContext.tokenContext;
+
+    uint8_t offset = 0;
+
+    if (dataLength <= HEADER_SIZE) {
+        PRINTF("Data too small for headers: expected at least %d, got %d\n",
+               HEADER_SIZE,
+               dataLength);
+        THROW(0x6A80);
+    }
+
+    enum Type type = workBuffer[offset];
+    PRINTF("Type: %d\n", type);
+    switch (type) {
+        case ETH_PLUGIN:
+            break;
+        default:
+            PRINTF("Unsupported type %d\n", type);
+            THROW(0x6a80);
+            break;
+    }
+    offset += TYPE_SIZE;
+
+    uint8_t version = workBuffer[offset];
+    PRINTF("version: %d\n", version);
+    switch (version) {
+        case VERSION_1:
+            break;
+        default:
+            PRINTF("Unsupported version %d\n", version);
+            THROW(0x6a80);
+            break;
+    }
+    offset += VERSION_SIZE;
+
+    uint8_t pluginNameLength = workBuffer[offset];
+    offset += PLUGIN_NAME_LENGTH_SIZE;
+
+    // Size of the payload (everything except the signature)
+    uint8_t payloadSize = HEADER_SIZE + pluginNameLength + ADDRESS_LENGTH + SELECTOR_SIZE +
+                          CHAIN_ID_SIZE + KEY_ID_SIZE + ALGORITHM_ID_SIZE;
+    if (dataLength < payloadSize) {
+        PRINTF("Data too small for payload: expected at least %d, got %d\n",
+               payloadSize,
+               dataLength);
+        THROW(0x6A80);
+    }
+
+    // `+ 1` because we want to add a null terminating character.
+    if (pluginNameLength + 1 > sizeof(tokenContext->pluginName)) {
+        PRINTF("plugin name too big: expected max %d, got %d\n",
+               sizeof(dataContext.tokenContext.pluginName),
+               pluginNameLength + 1);
+        THROW(0x6A80);
+    }
+
+    // Safe because we've checked the size before.
+    memcpy(tokenContext->pluginName, workBuffer + offset, pluginNameLength);
+    tokenContext->pluginName[pluginNameLength] = '\0';
+
+    PRINTF("Length: %d\n", pluginNameLength);
+    PRINTF("plugin name: %s\n", tokenContext->pluginName);
+    offset += pluginNameLength;
+
+    memcpy(tokenContext->contractAddress, workBuffer + offset, ADDRESS_LENGTH);
+    PRINTF("Address: %.*H\n", ADDRESS_LENGTH, workBuffer + offset);
+    offset += ADDRESS_LENGTH;
+
+    memcpy(tokenContext->methodSelector, workBuffer + offset, SELECTOR_SIZE);
+    PRINTF("Selector: %.*H\n", SELECTOR_SIZE, tokenContext->methodSelector);
+    offset += SELECTOR_SIZE;
+
+    uint64_t chainId = u64_from_BE(workBuffer + offset, CHAIN_ID_SIZE);
+    // this prints raw data, so to have a more meaningful print, display
+    // the buffer before the endianness swap
+    PRINTF("ChainID: %.*H\n", sizeof(chainId), (workBuffer + offset));
+    if ((chainConfig->chainId != 0) && (chainConfig->chainId != chainId)) {
+        PRINTF("Chain ID token mismatch\n");
+        THROW(0x6A80);
+    }
+    offset += CHAIN_ID_SIZE;
+
+    enum KeyId keyId = workBuffer[offset];
+    uint8_t const *rawKey;
+    uint8_t rawKeyLen;
+
+    PRINTF("KeyID: %d\n", keyId);
+    switch (keyId) {
+#ifdef HAVE_NFT_TESTING_KEY
+        case TEST_PLUGIN_KEY:
+#endif
+        case PROD_PLUGIN_KEY:
+            rawKey = LEDGER_NFT_SELECTOR_PUBLIC_KEY;
+            rawKeyLen = sizeof(LEDGER_NFT_SELECTOR_PUBLIC_KEY);
+            break;
+        default:
+            PRINTF("KeyID %d not supported\n", keyId);
+            THROW(0x6A80);
+            break;
+    }
+
+    PRINTF("RawKey: %.*H\n", rawKeyLen, rawKey);
+    offset += KEY_ID_SIZE;
+
+    uint8_t algorithmId = workBuffer[offset];
+    PRINTF("Algorithm: %d\n", algorithmId);
+    cx_curve_t curve;
+    verificationAlgo *verificationFn;
+    cx_md_t hashId;
+
+    switch (algorithmId) {
+        case ECC_SECG_P256K1__ECDSA_SHA_256:
+            curve = CX_CURVE_256K1;
+            verificationFn = (verificationAlgo *) cx_ecdsa_verify;
+            hashId = CX_SHA256;
+            break;
+        default:
+            PRINTF("Incorrect algorithmId %d\n", algorithmId);
+            THROW(0x6a80);
+            break;
+    }
+    offset += ALGORITHM_ID_SIZE;
+    PRINTF("hashing: %.*H\n", payloadSize, workBuffer);
+    cx_hash_sha256(workBuffer, payloadSize, hash, sizeof(hash));
+
+    if (dataLength < payloadSize + SIGNATURE_LENGTH_SIZE) {
+        PRINTF("Data too short to hold signature length\n");
+        THROW(0x6a80);
+    }
+
+    uint8_t signatureLen = workBuffer[offset];
+    PRINTF("Sigature len: %d\n", signatureLen);
+    if (signatureLen < MIN_DER_SIG_SIZE || signatureLen > MAX_DER_SIG_SIZE) {
+        PRINTF("SignatureLen too big or too small. Must be between %d and %d, got %d\n",
+               MIN_DER_SIG_SIZE,
+               MAX_DER_SIG_SIZE,
+               signatureLen);
+        THROW(0x6a80);
+    }
+    offset += SIGNATURE_LENGTH_SIZE;
+
+    if (dataLength < payloadSize + SIGNATURE_LENGTH_SIZE + signatureLen) {
+        PRINTF("Signature could not fit in data\n");
+        THROW(0x6a80);
+    }
+
+    cx_ecfp_init_public_key(curve, rawKey, rawKeyLen, &pluginKey);
+    if (!verificationFn(&pluginKey,
+                        CX_LAST,
+                        hashId,
+                        hash,
+                        sizeof(hash),
+                        workBuffer + offset,
+                        signatureLen)) {
+#ifndef HAVE_BYPASS_SIGNATURES
+        PRINTF("Invalid NFT signature\n");
+        THROW(0x6A80);
+#endif
+    }
+
+    pluginType = getPluginType(tokenContext->pluginName, pluginNameLength);
+    if (keyId == PROD_PLUGIN_KEY) {
+        if (pluginType != ERC721 && pluginType != ERC1155) {
+            PRINTF("AWS key must only be used to set NFT internal plugins\n");
+            THROW(0x6A80);
+        }
+    }
+
+    switch (pluginType) {
+        case EXTERNAL: {
+            PRINTF("Check external plugin %s\n", tokenContext->pluginName);
+
+            // Check if the plugin is present on the device
+            uint32_t params[2];
+            params[0] = (uint32_t) tokenContext->pluginName;
+            params[1] = ETH_PLUGIN_CHECK_PRESENCE;
+            BEGIN_TRY {
+                TRY {
+                    os_lib_call(params);
+                }
+                CATCH_OTHER(e) {
+                    PRINTF("%s external plugin is not present\n", tokenContext->pluginName);
+                    memset(tokenContext->pluginName, 0, sizeof(tokenContext->pluginName));
+                    THROW(0x6984);
+                }
+                FINALLY {
+                }
+            }
+            END_TRY;
+            break;
+        }
+        default:
+            break;
+    }
+
+    G_io_apdu_buffer[(*tx)++] = 0x90;
+    G_io_apdu_buffer[(*tx)++] = 0x00;
+}
diff --git a/src_features/signTx/cmd_signTx.c b/src_features/signTx/cmd_signTx.c
index 09c0f8c68..3b084f549 100644
--- a/src_features/signTx/cmd_signTx.c
+++ b/src_features/signTx/cmd_signTx.c
@@ -2,6 +2,7 @@
 #include "apdu_constants.h"
 #include "ui_flow.h"
 #include "feature_signTx.h"
+#include "eth_plugin_interface.h"
 
 void handleSign(uint8_t p1,
                 uint8_t p2,
@@ -12,6 +13,11 @@ void handleSign(uint8_t p1,
     UNUSED(tx);
     parserStatus_e txResult;
     uint32_t i;
+
+    if (os_global_pin_is_validated() != BOLOS_UX_OK) {
+        PRINTF("Device is PIN-locked");
+        THROW(0x6982);
+    }
     if (p1 == P1_FIRST) {
         if (dataLength < 1) {
             PRINTF("Invalid data\n");
@@ -53,7 +59,7 @@ void handleSign(uint8_t p1,
                 workBuffer++;
                 dataLength--;
             } else {
-                PRINTF("Transaction type not supported\n");
+                PRINTF("Transaction type %d not supported\n", txType);
                 THROW(0x6501);
             }
         } else {
diff --git a/src_features/signTx/logic_signTx.c b/src_features/signTx/logic_signTx.c
index 8b9339b25..6c65cff8e 100644
--- a/src_features/signTx/logic_signTx.c
+++ b/src_features/signTx/logic_signTx.c
@@ -65,6 +65,7 @@ customStatus_e customProcessor(txContext_t *context) {
             PRINTF("pluginstatus %d\n", dataContext.tokenContext.pluginStatus);
             eth_plugin_result_t status = dataContext.tokenContext.pluginStatus;
             if (status == ETH_PLUGIN_RESULT_ERROR) {
+                PRINTF("Plugin error\n");
                 return CUSTOM_FAULT;
             } else if (status >= ETH_PLUGIN_RESULT_SUCCESSFUL) {
                 dataContext.tokenContext.fieldIndex = 0;
@@ -113,6 +114,7 @@ customStatus_e customProcessor(txContext_t *context) {
                    copySize);
 
         if (context->currentFieldPos == context->currentFieldLength) {
+            PRINTF("\n\nIncrementing one\n");
             context->currentField++;
             context->processingField = false;
         }
@@ -238,7 +240,6 @@ static void feesToString(uint256_t *rawFee, char *displayBuffer, uint32_t displa
         i++;
     }
     displayBuffer[tickerOffset + i] = '\0';
-    PRINTF("Displayed fees: %s\n", displayBuffer);
 }
 
 // Compute the fees, transform it to a string, prepend a ticker to it and copy everything to
@@ -301,7 +302,6 @@ void finalizeParsing(bool direct) {
 
     // Verify the chain
     if (chainConfig->chainId != ETHEREUM_MAINNET_CHAINID) {
-        // TODO: Could we remove above check?
         uint64_t id = get_chain_id();
 
         if (chainConfig->chainId != id) {
@@ -338,24 +338,24 @@ void finalizeParsing(bool direct) {
             }
         }
         // Lookup tokens if requested
-        ethPluginProvideToken_t pluginProvideToken;
-        eth_plugin_prepare_provide_token(&pluginProvideToken);
+        ethPluginProvideInfo_t pluginProvideInfo;
+        eth_plugin_prepare_provide_info(&pluginProvideInfo);
         if ((pluginFinalize.tokenLookup1 != NULL) || (pluginFinalize.tokenLookup2 != NULL)) {
             if (pluginFinalize.tokenLookup1 != NULL) {
                 PRINTF("Lookup1: %.*H\n", ADDRESS_LENGTH, pluginFinalize.tokenLookup1);
-                pluginProvideToken.token1 = getKnownToken(pluginFinalize.tokenLookup1);
-                if (pluginProvideToken.token1 != NULL) {
-                    PRINTF("Token1 ticker: %s\n", pluginProvideToken.token1->ticker);
+                pluginProvideInfo.item1 = getKnownToken(pluginFinalize.tokenLookup1);
+                if (pluginProvideInfo.item1 != NULL) {
+                    PRINTF("Token1 ticker: %s\n", pluginProvideInfo.item1->token.ticker);
                 }
             }
             if (pluginFinalize.tokenLookup2 != NULL) {
                 PRINTF("Lookup2: %.*H\n", ADDRESS_LENGTH, pluginFinalize.tokenLookup2);
-                pluginProvideToken.token2 = getKnownToken(pluginFinalize.tokenLookup2);
-                if (pluginProvideToken.token2 != NULL) {
-                    PRINTF("Token2 ticker: %s\n", pluginProvideToken.token2->ticker);
+                pluginProvideInfo.item2 = getKnownToken(pluginFinalize.tokenLookup2);
+                if (pluginProvideInfo.item2 != NULL) {
+                    PRINTF("Token2 ticker: %s\n", pluginProvideInfo.item2->token.ticker);
                 }
             }
-            if (eth_plugin_call(ETH_PLUGIN_PROVIDE_TOKEN, (void *) &pluginProvideToken) <=
+            if (eth_plugin_call(ETH_PLUGIN_PROVIDE_INFO, (void *) &pluginProvideInfo) <=
                 ETH_PLUGIN_RESULT_UNSUCCESSFUL) {
                 PRINTF("Plugin provide token call failed\n");
                 reportFinalizeError(direct);
@@ -363,7 +363,7 @@ void finalizeParsing(bool direct) {
                     return;
                 }
             }
-            pluginFinalize.result = pluginProvideToken.result;
+            pluginFinalize.result = pluginProvideInfo.result;
         }
         if (pluginFinalize.result != ETH_PLUGIN_RESULT_FALLBACK) {
             // Handle the right interface
@@ -373,7 +373,7 @@ void finalizeParsing(bool direct) {
                     // Add the number of screens + the number of additional screens to get the total
                     // number of screens needed.
                     dataContext.tokenContext.pluginUiMaxItems =
-                        pluginFinalize.numScreens + pluginProvideToken.additionalScreens;
+                        pluginFinalize.numScreens + pluginProvideInfo.additionalScreens;
                     break;
                 case ETH_UI_TYPE_AMOUNT_ADDRESS:
                     genericUI = true;
@@ -389,9 +389,9 @@ void finalizeParsing(bool direct) {
                     tmpContent.txContent.value.length = 32;
                     memmove(tmpContent.txContent.destination, pluginFinalize.address, 20);
                     tmpContent.txContent.destinationLength = 20;
-                    if (pluginProvideToken.token1 != NULL) {
-                        decimals = pluginProvideToken.token1->decimals;
-                        ticker = pluginProvideToken.token1->ticker;
+                    if (pluginProvideInfo.item1 != NULL) {
+                        decimals = pluginProvideInfo.item1->token.decimals;
+                        ticker = pluginProvideInfo.item1->token.ticker;
                     }
                     break;
                 default:
diff --git a/src_features/stark_provideQuantum/cmd_stark_provideQuantum.c b/src_features/stark_provideQuantum/cmd_stark_provideQuantum.c
index 15f6d163c..4d390ddbf 100644
--- a/src_features/stark_provideQuantum/cmd_stark_provideQuantum.c
+++ b/src_features/stark_provideQuantum/cmd_stark_provideQuantum.c
@@ -37,19 +37,19 @@ void handleStarkwareProvideQuantum(uint8_t p1,
         addressZero = allzeroes(dataBuffer, 20);
     }
     if ((p1 != STARK_QUANTUM_ETH) && !addressZero) {
-        for (i = 0; i < MAX_TOKEN; i++) {
-            currentToken = &tmpCtx.transactionContext.tokens[i];
+        for (i = 0; i < MAX_ITEMS; i++) {
+            currentToken = &tmpCtx.transactionContext.extraInfo[i].token;
             if (tmpCtx.transactionContext.tokenSet[i] &&
                 (memcmp(currentToken->address, dataBuffer, 20) == 0)) {
                 break;
             }
         }
-        if (i == MAX_TOKEN) {
+        if (i == MAX_ITEMS) {
             PRINTF("Associated token not found\n");
             THROW(0x6A80);
         }
     } else {
-        i = MAX_TOKEN;
+        i = MAX_ITEMS;
     }
     memmove(dataContext.tokenContext.quantum, dataBuffer + 20, 32);
     if (p1 != STARK_QUANTUM_LEGACY) {
diff --git a/src_plugins/compound/compound_plugin.c b/src_plugins/compound/compound_plugin.c
index 66437a85b..a933d89ed 100644
--- a/src_plugins/compound/compound_plugin.c
+++ b/src_plugins/compound/compound_plugin.c
@@ -85,6 +85,7 @@ void compound_plugin_call(int message, void *parameters) {
             // enforce that ETH amount should be 0, except in ceth.mint case
             if (!allzeroes(msg->pluginSharedRO->txContent->value.value, 32)) {
                 if (context->selectorIndex != CETH_MINT) {
+                    PRINTF("Eth amount is not zero and token minted is not CETH!\n");
                     msg->result = ETH_PLUGIN_RESULT_ERROR;
                     break;
                 }
@@ -148,12 +149,12 @@ void compound_plugin_call(int message, void *parameters) {
             msg->result = ETH_PLUGIN_RESULT_OK;
         } break;
 
-        case ETH_PLUGIN_PROVIDE_TOKEN: {
-            ethPluginProvideToken_t *msg = (ethPluginProvideToken_t *) parameters;
+        case ETH_PLUGIN_PROVIDE_INFO: {
+            ethPluginProvideInfo_t *msg = (ethPluginProvideInfo_t *) parameters;
             compound_parameters_t *context = (compound_parameters_t *) msg->pluginContext;
-            PRINTF("compound plugin provide token: %d\n", (msg->token1 != NULL));
-            if (msg->token1 != NULL) {
-                strlcpy(context->ticker_1, msg->token1->ticker, MAX_TICKER_LEN);
+            PRINTF("compound plugin provide token: %d\n", (msg->item1 != NULL));
+            if (msg->item1 != NULL) {
+                strlcpy(context->ticker_1, msg->item1->token.ticker, MAX_TICKER_LEN);
                 switch (context->selectorIndex) {
                     case COMPOUND_REDEEM_UNDERLYING:
                     case COMPOUND_MINT:
@@ -166,7 +167,7 @@ void compound_plugin_call(int message, void *parameters) {
 
                     // Only case where we use the compound contract decimals
                     case COMPOUND_REDEEM:
-                        context->decimals = msg->token1->decimals;
+                        context->decimals = msg->item1->token.decimals;
                         msg->result = ETH_PLUGIN_RESULT_OK;
                         break;
 
diff --git a/src_plugins/erc1155/erc1155_plugin.c b/src_plugins/erc1155/erc1155_plugin.c
new file mode 100644
index 000000000..dbc1b8ef5
--- /dev/null
+++ b/src_plugins/erc1155/erc1155_plugin.c
@@ -0,0 +1,142 @@
+#ifdef HAVE_NFT_SUPPORT
+
+#include "erc1155_plugin.h"
+#include "eth_plugin_internal.h"
+
+static const uint8_t ERC1155_APPROVE_FOR_ALL_SELECTOR[SELECTOR_SIZE] = {0xa2, 0x2c, 0xb4, 0x65};
+static const uint8_t ERC1155_SAFE_TRANSFER_SELECTOR[SELECTOR_SIZE] = {0xf2, 0x42, 0x43, 0x2a};
+static const uint8_t ERC1155_SAFE_BATCH_TRANSFER[SELECTOR_SIZE] = {0x2e, 0xb2, 0xc2, 0xd6};
+
+const uint8_t *const ERC1155_SELECTORS[NUM_ERC1155_SELECTORS] = {
+    ERC1155_APPROVE_FOR_ALL_SELECTOR,
+    ERC1155_SAFE_TRANSFER_SELECTOR,
+    ERC1155_SAFE_BATCH_TRANSFER,
+};
+
+static void handle_init_contract(void *parameters) {
+    ethPluginInitContract_t *msg = (ethPluginInitContract_t *) parameters;
+    erc1155_context_t *context = (erc1155_context_t *) msg->pluginContext;
+
+    uint8_t i;
+    for (i = 0; i < NUM_ERC1155_SELECTORS; i++) {
+        if (memcmp((uint8_t *) PIC(ERC1155_SELECTORS[i]), msg->selector, SELECTOR_SIZE) == 0) {
+            context->selectorIndex = i;
+            break;
+        }
+    }
+
+    // No selector found.
+    if (i == NUM_ERC1155_SELECTORS) {
+        PRINTF("Unknown erc1155 selector %.*H\n", SELECTOR_SIZE, msg->selector);
+        msg->result = ETH_PLUGIN_RESULT_FALLBACK;
+        return;
+    }
+
+    msg->result = ETH_PLUGIN_RESULT_OK;
+    switch (context->selectorIndex) {
+        case SAFE_TRANSFER:
+        case SAFE_BATCH_TRANSFER:
+            context->next_param = FROM;
+            break;
+        case SET_APPROVAL_FOR_ALL:
+            context->next_param = OPERATOR;
+            break;
+        default:
+            PRINTF("Unsupported selector index: %d\n", context->selectorIndex);
+            msg->result = ETH_PLUGIN_RESULT_ERROR;
+            break;
+    }
+}
+
+static void handle_finalize(void *parameters) {
+    ethPluginFinalize_t *msg = (ethPluginFinalize_t *) parameters;
+    erc1155_context_t *context = (erc1155_context_t *) msg->pluginContext;
+
+    if (context->selectorIndex != SAFE_BATCH_TRANSFER) {
+        msg->tokenLookup1 = msg->pluginSharedRO->txContent->destination;
+    } else {
+        msg->tokenLookup1 = NULL;
+    }
+
+    msg->tokenLookup2 = NULL;
+    switch (context->selectorIndex) {
+        case SAFE_TRANSFER:
+            msg->numScreens = 5;
+            break;
+        case SET_APPROVAL_FOR_ALL:
+        case SAFE_BATCH_TRANSFER:
+            msg->numScreens = 4;
+            break;
+        default:
+            msg->result = ETH_PLUGIN_RESULT_ERROR;
+            return;
+    }
+    // Check if some ETH is attached to this tx
+    if (!allzeroes((void *) &msg->pluginSharedRO->txContent->value,
+                   sizeof(msg->pluginSharedRO->txContent->value))) {
+        // Those functions are not payable so return an error.
+        msg->result = ETH_PLUGIN_RESULT_ERROR;
+        return;
+    }
+    msg->uiType = ETH_UI_TYPE_GENERIC;
+    msg->result = ETH_PLUGIN_RESULT_OK;
+}
+
+static void handle_provide_info(void *parameters) {
+    ethPluginProvideInfo_t *msg = (ethPluginProvideInfo_t *) parameters;
+
+    msg->result = ETH_PLUGIN_RESULT_OK;
+}
+
+static void handle_query_contract_id(void *parameters) {
+    ethQueryContractID_t *msg = (ethQueryContractID_t *) parameters;
+    erc1155_context_t *context = (erc1155_context_t *) msg->pluginContext;
+
+    msg->result = ETH_PLUGIN_RESULT_OK;
+
+    strlcpy(msg->name, "NFT", msg->nameLength);
+
+    switch (context->selectorIndex) {
+        case SET_APPROVAL_FOR_ALL:
+            strlcpy(msg->version, "Allowance", msg->versionLength);
+            break;
+        case SAFE_TRANSFER:
+            strlcpy(msg->version, "Transfer", msg->versionLength);
+            break;
+        case SAFE_BATCH_TRANSFER:
+            strlcpy(msg->version, "Batch Transfer", msg->versionLength);
+            break;
+        default:
+            PRINTF("Unsupported selector %d\n", context->selectorIndex);
+            msg->result = ETH_PLUGIN_RESULT_ERROR;
+            break;
+    }
+}
+
+void erc1155_plugin_call(int message, void *parameters) {
+    switch (message) {
+        case ETH_PLUGIN_INIT_CONTRACT: {
+            handle_init_contract(parameters);
+        } break;
+        case ETH_PLUGIN_PROVIDE_PARAMETER: {
+            handle_provide_parameter_1155(parameters);
+        } break;
+        case ETH_PLUGIN_FINALIZE: {
+            handle_finalize(parameters);
+        } break;
+        case ETH_PLUGIN_PROVIDE_INFO: {
+            handle_provide_info(parameters);
+        } break;
+        case ETH_PLUGIN_QUERY_CONTRACT_ID: {
+            handle_query_contract_id(parameters);
+        } break;
+        case ETH_PLUGIN_QUERY_CONTRACT_UI: {
+            handle_query_contract_ui_1155(parameters);
+        } break;
+        default:
+            PRINTF("Unhandled message %d\n", message);
+            break;
+    }
+}
+
+#endif  // HAVE_NFT_SUPPORT
diff --git a/src_plugins/erc1155/erc1155_plugin.h b/src_plugins/erc1155/erc1155_plugin.h
new file mode 100644
index 000000000..0702fdf70
--- /dev/null
+++ b/src_plugins/erc1155/erc1155_plugin.h
@@ -0,0 +1,55 @@
+#ifdef HAVE_NFT_SUPPORT
+
+#pragma once
+
+#include <string.h>
+#include "eth_plugin_handler.h"
+#include "shared_context.h"
+#include "ethUtils.h"
+#include "utils.h"
+#include "uint256.h"
+
+// Internal plugin for EIP 1155: https://eips.ethereum.org/EIPS/eip-1155
+
+#define NUM_ERC1155_SELECTORS 3
+
+typedef enum {
+    SET_APPROVAL_FOR_ALL,
+    SAFE_TRANSFER,
+    SAFE_BATCH_TRANSFER,
+} erc1155_selector_t;
+
+typedef enum {
+    FROM,
+    TO,
+    TOKEN_IDS_OFFSET,
+    TOKEN_IDS_LENGTH,
+    TOKEN_ID,
+    VALUE_OFFSET,
+    VALUE_LENGTH,
+    VALUE,
+    OPERATOR,
+    APPROVED,
+    NONE,
+} erc1155_selector_field;
+
+typedef struct erc1155_context_t {
+    uint8_t address[ADDRESS_LENGTH];
+    uint8_t tokenId[INT256_LENGTH];
+    uint256_t value;
+
+    uint16_t ids_array_len;
+    uint32_t ids_offset;
+    uint16_t values_array_len;
+    uint32_t values_offset;
+    uint16_t array_index;
+
+    bool approved;
+    erc1155_selector_field next_param;
+    uint8_t selectorIndex;
+} erc1155_context_t;
+
+void handle_provide_parameter_1155(void *parameters);
+void handle_query_contract_ui_1155(void *parameters);
+
+#endif  // HAVE_NFT_SUPPORT
diff --git a/src_plugins/erc1155/erc1155_provide_parameters.c b/src_plugins/erc1155/erc1155_provide_parameters.c
new file mode 100644
index 000000000..800aafb73
--- /dev/null
+++ b/src_plugins/erc1155/erc1155_provide_parameters.c
@@ -0,0 +1,147 @@
+#ifdef HAVE_NFT_SUPPORT
+
+#include "erc1155_plugin.h"
+#include "eth_plugin_internal.h"
+
+static void handle_safe_transfer(ethPluginProvideParameter_t *msg, erc1155_context_t *context) {
+    uint8_t new_value[INT256_LENGTH];
+
+    switch (context->next_param) {
+        case FROM:
+            context->next_param = TO;
+            break;
+        case TO:
+            copy_address(context->address, msg->parameter, sizeof(context->address));
+            context->next_param = TOKEN_ID;
+            break;
+        case TOKEN_ID:
+            copy_parameter(context->tokenId, msg->parameter, sizeof(context->tokenId));
+            context->next_param = VALUE;
+            break;
+        case VALUE:
+            copy_parameter(new_value, msg->parameter, sizeof(new_value));
+            convertUint256BE(new_value, INT256_LENGTH, &context->value);
+            context->next_param = NONE;
+            break;
+        default:
+            // Some extra data might be present so don't error.
+            break;
+    }
+}
+
+static void handle_batch_transfer(ethPluginProvideParameter_t *msg, erc1155_context_t *context) {
+    uint256_t new_value;
+
+    switch (context->next_param) {
+        case FROM:
+            context->next_param = TO;
+            break;
+        case TO:
+            copy_address(context->address, msg->parameter, sizeof(context->address));
+            context->next_param = TOKEN_IDS_OFFSET;
+            break;
+        case TOKEN_IDS_OFFSET:
+            context->ids_offset =
+                U4BE(msg->parameter, PARAMETER_LENGTH - sizeof(context->ids_offset)) + 4;
+            context->next_param = VALUE_OFFSET;
+            break;
+        case VALUE_OFFSET:
+            context->values_offset =
+                U4BE(msg->parameter, PARAMETER_LENGTH - sizeof(context->values_offset)) + 4;
+            context->next_param = TOKEN_IDS_LENGTH;
+            break;
+        case TOKEN_IDS_LENGTH:
+            if ((msg->parameterOffset + PARAMETER_LENGTH) > context->ids_offset) {
+                context->ids_array_len =
+                    U2BE(msg->parameter, PARAMETER_LENGTH - sizeof(context->ids_array_len));
+                context->next_param = TOKEN_ID;
+                // set to zero for next step
+                context->array_index = 0;
+            }
+            break;
+        case TOKEN_ID:
+            // don't copy anything since we won't display it
+            if (--context->ids_array_len == 0) {
+                context->next_param = VALUE_LENGTH;
+            }
+            context->array_index++;
+            break;
+        case VALUE_LENGTH:
+            if ((msg->parameterOffset + PARAMETER_LENGTH) > context->values_offset) {
+                context->values_array_len =
+                    U2BE(msg->parameter, PARAMETER_LENGTH - sizeof(context->values_array_len));
+                if (context->values_array_len != context->array_index) {
+                    PRINTF("Token ids and values array sizes mismatch!");
+                }
+                context->next_param = VALUE;
+                // set to zero for next step
+                context->array_index = 0;
+                explicit_bzero(&context->value, sizeof(context->value));
+            }
+            break;
+        case VALUE:
+            // put it temporarily in token id since we don't use it in batch transfer
+            copy_parameter(context->tokenId, msg->parameter, sizeof(context->value));
+            convertUint256BE(context->tokenId, sizeof(context->tokenId), &new_value);
+            add256(&context->value, &new_value, &context->value);
+            if (--context->values_array_len == 0) {
+                context->next_param = NONE;
+            }
+            context->array_index++;
+            break;
+        default:
+            // Some extra data might be present so don't error.
+            break;
+    }
+}
+
+static void handle_approval_for_all(ethPluginProvideParameter_t *msg, erc1155_context_t *context) {
+    switch (context->next_param) {
+        case OPERATOR:
+            context->next_param = APPROVED;
+            copy_address(context->address, msg->parameter, sizeof(context->address));
+            break;
+        case APPROVED:
+            context->approved = msg->parameter[PARAMETER_LENGTH - 1];
+            context->next_param = NONE;
+            break;
+        default:
+            PRINTF("Param %d not supported\n", context->next_param);
+            msg->result = ETH_PLUGIN_RESULT_ERROR;
+            break;
+    }
+}
+
+void handle_provide_parameter_1155(void *parameters) {
+    ethPluginProvideParameter_t *msg = (ethPluginProvideParameter_t *) parameters;
+    erc1155_context_t *context = (erc1155_context_t *) msg->pluginContext;
+
+    PRINTF("erc1155 plugin provide parameter %d %.*H\n",
+           msg->parameterOffset,
+           PARAMETER_LENGTH,
+           msg->parameter);
+
+    msg->result = ETH_PLUGIN_RESULT_SUCCESSFUL;
+
+    // if (context->targetOffset > SELECTOR_SIZE &&
+    //     context->targetOffset != msg->parameterOffset - SELECTOR_SIZE) {
+    //     return;
+    // }
+    switch (context->selectorIndex) {
+        case SAFE_TRANSFER:
+            handle_safe_transfer(msg, context);
+            break;
+        case SAFE_BATCH_TRANSFER:
+            handle_batch_transfer(msg, context);
+            break;
+        case SET_APPROVAL_FOR_ALL:
+            handle_approval_for_all(msg, context);
+            break;
+        default:
+            PRINTF("Selector index %d not supported\n", context->selectorIndex);
+            msg->result = ETH_PLUGIN_RESULT_ERROR;
+            break;
+    }
+}
+
+#endif  // HAVE_NFT_SUPPORT
diff --git a/src_plugins/erc1155/erc1155_ui.c b/src_plugins/erc1155/erc1155_ui.c
new file mode 100644
index 000000000..5b4e941f3
--- /dev/null
+++ b/src_plugins/erc1155/erc1155_ui.c
@@ -0,0 +1,152 @@
+#ifdef HAVE_NFT_SUPPORT
+
+#include "erc1155_plugin.h"
+
+static void set_approval_for_all_ui(ethQueryContractUI_t *msg, erc1155_context_t *context) {
+    switch (msg->screenIndex) {
+        case 0:
+            if (context->approved) {
+                strlcpy(msg->title, "Allow", msg->titleLength);
+            } else {
+                strlcpy(msg->title, "Revoke", msg->titleLength);
+            }
+            getEthDisplayableAddress(context->address,
+                                     msg->msg,
+                                     msg->msgLength,
+                                     &global_sha3,
+                                     chainConfig->chainId);
+            break;
+        case 1:
+            strlcpy(msg->title, "To Manage ALL", msg->titleLength);
+            if (msg->item1) {
+                strlcpy(msg->msg, (const char *) &msg->item1->nft.collectionName, msg->msgLength);
+            } else {
+                strlcpy(msg->msg, "Not found", msg->msgLength);
+            }
+            break;
+        case 2:
+            strlcpy(msg->title, "NFT Address", msg->titleLength);
+            getEthDisplayableAddress(msg->pluginSharedRO->txContent->destination,
+                                     msg->msg,
+                                     msg->msgLength,
+                                     &global_sha3,
+                                     chainConfig->chainId);
+            break;
+        default:
+            PRINTF("Unsupported screen index %d\n", msg->screenIndex);
+            msg->result = ETH_PLUGIN_RESULT_ERROR;
+            break;
+    }
+}
+
+static void set_transfer_ui(ethQueryContractUI_t *msg, erc1155_context_t *context) {
+    switch (msg->screenIndex) {
+        case 0:
+            strlcpy(msg->title, "To", msg->titleLength);
+            getEthDisplayableAddress(context->address,
+                                     msg->msg,
+                                     msg->msgLength,
+                                     &global_sha3,
+                                     chainConfig->chainId);
+            break;
+        case 1:
+            strlcpy(msg->title, "Collection Name", msg->titleLength);
+            if (msg->item1) {
+                strlcpy(msg->msg, (const char *) &msg->item1->nft.collectionName, msg->msgLength);
+            } else {
+                strlcpy(msg->msg, "Not Found", msg->msgLength);
+            }
+            break;
+        case 2:
+            strlcpy(msg->title, "NFT Address", msg->titleLength);
+            getEthDisplayableAddress((uint8_t *) msg->item1->nft.contractAddress,
+                                     msg->msg,
+                                     msg->msgLength,
+                                     &global_sha3,
+                                     chainConfig->chainId);
+            break;
+        case 3:
+            strlcpy(msg->title, "NFT ID", msg->titleLength);
+            uint256_to_decimal(context->tokenId,
+                               sizeof(context->tokenId),
+                               msg->msg,
+                               msg->msgLength);
+            break;
+        case 4:
+            strlcpy(msg->title, "Quantity", msg->titleLength);
+            tostring256(&context->value, 10, msg->msg, msg->msgLength);
+            break;
+        default:
+            PRINTF("Unsupported screen index %d\n", msg->screenIndex);
+            msg->result = ETH_PLUGIN_RESULT_ERROR;
+            break;
+    }
+}
+
+static void set_batch_transfer_ui(ethQueryContractUI_t *msg, erc1155_context_t *context) {
+    char quantity_str[48];
+
+    switch (msg->screenIndex) {
+        case 0:
+            strlcpy(msg->title, "To", msg->titleLength);
+            getEthDisplayableAddress(context->address,
+                                     msg->msg,
+                                     msg->msgLength,
+                                     &global_sha3,
+                                     chainConfig->chainId);
+            break;
+        case 1:
+            strlcpy(msg->title, "Collection Name", msg->titleLength);
+            if (msg->item1) {
+                strlcpy(msg->msg, (const char *) &msg->item1->nft.collectionName, msg->msgLength);
+            } else {
+                strlcpy(msg->msg, "Not Found", msg->msgLength);
+            }
+            break;
+        case 2:
+            strlcpy(msg->title, "NFT Address", msg->titleLength);
+            getEthDisplayableAddress((uint8_t *) msg->item1->nft.contractAddress,
+                                     msg->msg,
+                                     msg->msgLength,
+                                     &global_sha3,
+                                     chainConfig->chainId);
+            break;
+        case 3:
+            strlcpy(msg->title, "Total Quantity", msg->titleLength);
+            tostring256(&context->value, 10, &quantity_str[0], sizeof(quantity_str));
+            snprintf(msg->msg,
+                     msg->msgLength,
+                     "%s from %d NFT IDs",
+                     quantity_str,
+                     context->array_index);
+            break;
+        default:
+            PRINTF("Unsupported screen index %d\n", msg->screenIndex);
+            msg->result = ETH_PLUGIN_RESULT_ERROR;
+            break;
+    }
+}
+
+void handle_query_contract_ui_1155(void *parameters) {
+    ethQueryContractUI_t *msg = (ethQueryContractUI_t *) parameters;
+    erc1155_context_t *context = (erc1155_context_t *) msg->pluginContext;
+
+    msg->result = ETH_PLUGIN_RESULT_OK;
+    switch (context->selectorIndex) {
+        case SET_APPROVAL_FOR_ALL:
+            set_approval_for_all_ui(msg, context);
+            break;
+        case SAFE_TRANSFER:
+            set_transfer_ui(msg, context);
+            break;
+        case SAFE_BATCH_TRANSFER:
+            set_batch_transfer_ui(msg, context);
+            break;
+        default:
+            msg->result = ETH_PLUGIN_RESULT_ERROR;
+            PRINTF("Unsupported selector index %d\n", context->selectorIndex);
+            break;
+    }
+}
+
+#endif  // HAVE_NFT_SUPPORT
diff --git a/src_plugins/erc20/erc20_plugin.c b/src_plugins/erc20/erc20_plugin.c
index 7903402a0..04062d82e 100644
--- a/src_plugins/erc20/erc20_plugin.c
+++ b/src_plugins/erc20/erc20_plugin.c
@@ -159,16 +159,16 @@ void erc20_plugin_call(int message, void *parameters) {
             }
         } break;
 
-        case ETH_PLUGIN_PROVIDE_TOKEN: {
-            ethPluginProvideToken_t *msg = (ethPluginProvideToken_t *) parameters;
+        case ETH_PLUGIN_PROVIDE_INFO: {
+            ethPluginProvideInfo_t *msg = (ethPluginProvideInfo_t *) parameters;
             erc20_parameters_t *context = (erc20_parameters_t *) msg->pluginContext;
             PRINTF("erc20 plugin provide token 1: %d - 2: %d\n",
-                   (msg->token1 != NULL),
-                   (msg->token2 != NULL));
-            if (msg->token1 != NULL) {
+                   (msg->item1 != NULL),
+                   (msg->item2 != NULL));
+            if (msg->item1 != NULL) {
                 context->target = TARGET_ADDRESS;
-                strlcpy(context->ticker, msg->token1->ticker, MAX_TICKER_LEN);
-                context->decimals = msg->token1->decimals;
+                strlcpy(context->ticker, msg->item1->token.ticker, MAX_TICKER_LEN);
+                context->decimals = msg->item1->token.decimals;
                 if (context->selectorIndex == ERC20_APPROVE) {
                     if (check_contract(context)) {
                         context->target = TARGET_CONTRACT;
diff --git a/src_plugins/erc721/erc721_plugin.c b/src_plugins/erc721/erc721_plugin.c
index 9d04d744b..badddb3f6 100644
--- a/src_plugins/erc721/erc721_plugin.c
+++ b/src_plugins/erc721/erc721_plugin.c
@@ -1,151 +1,150 @@
-#include <string.h>
+#ifdef HAVE_NFT_SUPPORT
+
+#include "erc721_plugin.h"
 #include "eth_plugin_internal.h"
-#include "eth_plugin_handler.h"
-#include "shared_context.h"
-#include "ethUtils.h"
-#include "utils.h"
-
-typedef struct erc721_parameters_t {
-    uint8_t selectorIndex;
-    uint8_t address[ADDRESS_LENGTH];
-    uint8_t tokenId[INT256_LENGTH];
-    // tokenDefinition_t *tokenSelf;
-    // tokenDefinition_t *tokenAddress;
-} erc721_parameters_t;
-
-bool erc721_plugin_available_check() {
-#ifdef HAVE_STARKWARE
-    if (quantumSet) {
-        switch (dataContext.tokenContext.quantumType) {
-            case STARK_QUANTUM_ERC721:
-            case STARK_QUANTUM_MINTABLE_ERC721:
-                return true;
-            default:
-                return false;
+
+static const uint8_t ERC721_APPROVE_SELECTOR[SELECTOR_SIZE] = {0x09, 0x5e, 0xa7, 0xb3};
+static const uint8_t ERC721_APPROVE_FOR_ALL_SELECTOR[SELECTOR_SIZE] = {0xa2, 0x2c, 0xb4, 0x65};
+static const uint8_t ERC721_TRANSFER_SELECTOR[SELECTOR_SIZE] = {0x23, 0xb8, 0x72, 0xdd};
+static const uint8_t ERC721_SAFE_TRANSFER_SELECTOR[SELECTOR_SIZE] = {0x42, 0x84, 0x2e, 0x0e};
+static const uint8_t ERC721_SAFE_TRANSFER_DATA_SELECTOR[SELECTOR_SIZE] = {0xb8, 0x8d, 0x4f, 0xde};
+
+const uint8_t *const ERC721_SELECTORS[NUM_ERC721_SELECTORS] = {
+    ERC721_APPROVE_SELECTOR,
+    ERC721_APPROVE_FOR_ALL_SELECTOR,
+    ERC721_TRANSFER_SELECTOR,
+    ERC721_SAFE_TRANSFER_SELECTOR,
+    ERC721_SAFE_TRANSFER_DATA_SELECTOR,
+};
+
+static void handle_init_contract(void *parameters) {
+    ethPluginInitContract_t *msg = (ethPluginInitContract_t *) parameters;
+    erc721_context_t *context = (erc721_context_t *) msg->pluginContext;
+
+    uint8_t i;
+    for (i = 0; i < NUM_ERC721_SELECTORS; i++) {
+        if (memcmp((uint8_t *) PIC(ERC721_SELECTORS[i]), msg->selector, SELECTOR_SIZE) == 0) {
+            context->selectorIndex = i;
+            break;
+        }
+    }
+
+    // No selector found.
+    if (i == NUM_ERC721_SELECTORS) {
+        PRINTF("Unknown erc721 selector %.*H\n", SELECTOR_SIZE, msg->selector);
+        msg->result = ETH_PLUGIN_RESULT_FALLBACK;
+        return;
+    }
+
+    msg->result = ETH_PLUGIN_RESULT_OK;
+    switch (context->selectorIndex) {
+        case SET_APPROVAL_FOR_ALL:
+        case APPROVE:
+            context->next_param = OPERATOR;
+            break;
+        case SAFE_TRANSFER:
+        case SAFE_TRANSFER_DATA:
+        case TRANSFER:
+            context->next_param = FROM;
+            break;
+        default:
+            PRINTF("Unsupported selector index: %d\n", context->selectorIndex);
+            msg->result = ETH_PLUGIN_RESULT_ERROR;
+            break;
+    }
+}
+
+static void handle_finalize(void *parameters) {
+    ethPluginFinalize_t *msg = (ethPluginFinalize_t *) parameters;
+    erc721_context_t *context = (erc721_context_t *) msg->pluginContext;
+
+    msg->tokenLookup1 = msg->pluginSharedRO->txContent->destination;
+    msg->tokenLookup2 = NULL;
+    switch (context->selectorIndex) {
+        case TRANSFER:
+        case SAFE_TRANSFER:
+        case SAFE_TRANSFER_DATA:
+        case APPROVE:
+            msg->numScreens = 4;
+            break;
+        case SET_APPROVAL_FOR_ALL:
+            msg->numScreens = 3;
+            break;
+        default:
+            msg->result = ETH_PLUGIN_RESULT_ERROR;
+            return;
+    }
+    // Check if some ETH is attached to this tx
+    if (!allzeroes((void *) &msg->pluginSharedRO->txContent->value,
+                   sizeof(msg->pluginSharedRO->txContent->value))) {
+        // Set Approval for All is not payable
+        if (context->selectorIndex == SET_APPROVAL_FOR_ALL) {
+            msg->result = ETH_PLUGIN_RESULT_ERROR;
+            return;
+        } else {
+            // Add an additional screen
+            msg->numScreens++;
         }
     }
-    return false;
-#endif
+    msg->uiType = ETH_UI_TYPE_GENERIC;
+    msg->result = ETH_PLUGIN_RESULT_OK;
+}
+
+static void handle_provide_info(void *parameters) {
+    ethPluginProvideInfo_t *msg = (ethPluginProvideInfo_t *) parameters;
+
+    msg->result = ETH_PLUGIN_RESULT_OK;
+}
+
+static void handle_query_contract_id(void *parameters) {
+    ethQueryContractID_t *msg = (ethQueryContractID_t *) parameters;
+    erc721_context_t *context = (erc721_context_t *) msg->pluginContext;
+
+    msg->result = ETH_PLUGIN_RESULT_OK;
+
+    strlcpy(msg->name, "NFT", msg->nameLength);
+
+    switch (context->selectorIndex) {
+        case SET_APPROVAL_FOR_ALL:
+        case APPROVE:
+            strlcpy(msg->version, "Allowance", msg->versionLength);
+            break;
+        case SAFE_TRANSFER:
+        case SAFE_TRANSFER_DATA:
+        case TRANSFER:
+            strlcpy(msg->version, "Transfer", msg->versionLength);
+            break;
+        default:
+            PRINTF("Unsupported selector %d\n", context->selectorIndex);
+            msg->result = ETH_PLUGIN_RESULT_ERROR;
+            break;
+    }
 }
 
 void erc721_plugin_call(int message, void *parameters) {
     switch (message) {
         case ETH_PLUGIN_INIT_CONTRACT: {
-            ethPluginInitContract_t *msg = (ethPluginInitContract_t *) parameters;
-            erc721_parameters_t *context = (erc721_parameters_t *) msg->pluginContext;
-            // enforce that ETH amount should be 0
-            if (!allzeroes(msg->pluginSharedRO->txContent->value.value, 32)) {
-                PRINTF("Err: Transaction amount is not 0 for erc721 approval\n");
-                msg->result = ETH_PLUGIN_RESULT_ERROR;
-            } else {
-                size_t i;
-                for (i = 0; i < NUM_ERC721_SELECTORS; i++) {
-                    if (memcmp((uint8_t *) PIC(ERC721_SELECTORS[i]),
-                               msg->selector,
-                               SELECTOR_SIZE) == 0) {
-                        context->selectorIndex = i;
-                        break;
-                    }
-                }
-                if (i == NUM_ERC721_SELECTORS) {
-                    PRINTF("Unknown erc721 selector %.*H\n", SELECTOR_SIZE, msg->selector);
-                    break;
-                }
-                if (msg->dataSize != 4 + 32 + 32) {
-                    PRINTF("Invalid erc721 approval data size %d\n", msg->dataSize);
-                    break;
-                }
-                PRINTF("erc721 plugin init\n");
-                msg->result = ETH_PLUGIN_RESULT_OK;
-            }
+            handle_init_contract(parameters);
         } break;
-
         case ETH_PLUGIN_PROVIDE_PARAMETER: {
-            ethPluginProvideParameter_t *msg = (ethPluginProvideParameter_t *) parameters;
-            erc721_parameters_t *context = (erc721_parameters_t *) msg->pluginContext;
-            PRINTF("erc721 plugin provide parameter %d %.*H\n",
-                   msg->parameterOffset,
-                   32,
-                   msg->parameter);
-            switch (msg->parameterOffset) {
-                case 4:
-                    memmove(context->address, msg->parameter + 32 - 20, 20);
-                    msg->result = ETH_PLUGIN_RESULT_OK;
-                    break;
-                case 4 + 32:
-                    memmove(context->tokenId, msg->parameter, 32);
-                    msg->result = ETH_PLUGIN_RESULT_OK;
-                    break;
-                default:
-                    PRINTF("Unhandled parameter offset\n");
-                    break;
-            }
+            handle_provide_parameter_721(parameters);
         } break;
-
         case ETH_PLUGIN_FINALIZE: {
-            ethPluginFinalize_t *msg = (ethPluginFinalize_t *) parameters;
-            erc721_parameters_t *context = (erc721_parameters_t *) msg->pluginContext;
-            PRINTF("erc721 plugin finalize\n");
-            msg->tokenLookup1 = msg->pluginSharedRO->txContent->destination;
-            msg->tokenLookup2 = context->address;
-            msg->numScreens = 3;
-            msg->uiType = ETH_UI_TYPE_GENERIC;
-            msg->result = ETH_PLUGIN_RESULT_OK;
+            handle_finalize(parameters);
         } break;
-
-        case ETH_PLUGIN_PROVIDE_TOKEN: {
-            ethPluginProvideToken_t *msg = (ethPluginProvideToken_t *) parameters;
-            PRINTF("erc721 plugin provide token dest: %d - address: %d\n",
-                   (msg->token1 != NULL),
-                   (msg->token2 != NULL));
-            // context->tokenSelf = msg->token1;
-            // context->tokenAddress = msg->token2;
-            msg->result = ETH_PLUGIN_RESULT_OK;
+        case ETH_PLUGIN_PROVIDE_INFO: {
+            handle_provide_info(parameters);
         } break;
-
         case ETH_PLUGIN_QUERY_CONTRACT_ID: {
-            ethQueryContractID_t *msg = (ethQueryContractID_t *) parameters;
-            strlcpy(msg->name, "Allowance", msg->nameLength);
-            strlcpy(msg->version, "", msg->versionLength);
-            msg->result = ETH_PLUGIN_RESULT_OK;
+            handle_query_contract_id(parameters);
         } break;
-
         case ETH_PLUGIN_QUERY_CONTRACT_UI: {
-            ethQueryContractUI_t *msg = (ethQueryContractUI_t *) parameters;
-            erc721_parameters_t *context = (erc721_parameters_t *) msg->pluginContext;
-            switch (msg->screenIndex) {
-                case 0:
-                    strlcpy(msg->title, "Contract Name", msg->titleLength);
-                    getEthDisplayableAddress(tmpContent.txContent.destination,
-                                             msg->msg,
-                                             msg->msgLength,
-                                             &global_sha3,
-                                             chainConfig->chainId);
-                    msg->result = ETH_PLUGIN_RESULT_OK;
-                    break;
-
-                case 1:
-                    strlcpy(msg->title, "NFT Contract", msg->titleLength);
-                    getEthDisplayableAddress(context->address,
-                                             msg->msg,
-                                             msg->msgLength,
-                                             &global_sha3,
-                                             chainConfig->chainId);
-                    msg->result = ETH_PLUGIN_RESULT_OK;
-                    break;
-
-                case 2:
-                    strlcpy(msg->title, "TokenID", msg->titleLength);
-                    snprintf(msg->msg, 70, "0x%.*H", 32, context->tokenId);
-                    msg->result = ETH_PLUGIN_RESULT_OK;
-                    break;
-
-                default:
-                    break;
-            }
+            handle_query_contract_ui_721(parameters);
         } break;
-
         default:
             PRINTF("Unhandled message %d\n", message);
+            break;
     }
 }
+
+#endif  // HAVE_NFT_SUPPORT
diff --git a/src_plugins/erc721/erc721_plugin.h b/src_plugins/erc721/erc721_plugin.h
new file mode 100644
index 000000000..1846609eb
--- /dev/null
+++ b/src_plugins/erc721/erc721_plugin.h
@@ -0,0 +1,46 @@
+#ifdef HAVE_NFT_SUPPORT
+
+#pragma once
+
+#include <string.h>
+#include "eth_plugin_handler.h"
+#include "shared_context.h"
+#include "ethUtils.h"
+#include "utils.h"
+
+// Internal plugin for EIP 721: https://eips.ethereum.org/EIPS/eip-721
+
+#define NUM_ERC721_SELECTORS 5
+
+typedef enum {
+    APPROVE,
+    SET_APPROVAL_FOR_ALL,
+    TRANSFER,
+    SAFE_TRANSFER,
+    SAFE_TRANSFER_DATA,
+} erc721_selector_t;
+
+typedef enum {
+    FROM,
+    TO,
+    DATA,
+    TOKEN_ID,
+    OPERATOR,
+    APPROVED,
+    NONE,
+} erc721_selector_field;
+
+typedef struct erc721_context_t {
+    uint8_t address[ADDRESS_LENGTH];
+    uint8_t tokenId[INT256_LENGTH];
+
+    bool approved;
+
+    erc721_selector_field next_param;
+    uint8_t selectorIndex;
+} erc721_context_t;
+
+void handle_provide_parameter_721(void *parameters);
+void handle_query_contract_ui_721(void *parameters);
+
+#endif  // HAVE_NFT_SUPPORT
diff --git a/src_plugins/erc721/erc721_provide_parameters.c b/src_plugins/erc721/erc721_provide_parameters.c
new file mode 100644
index 000000000..ac5b8701d
--- /dev/null
+++ b/src_plugins/erc721/erc721_provide_parameters.c
@@ -0,0 +1,97 @@
+#ifdef HAVE_NFT_SUPPORT
+
+#include "erc721_plugin.h"
+#include "eth_plugin_internal.h"
+
+static void handle_approve(ethPluginProvideParameter_t *msg, erc721_context_t *context) {
+    switch (context->next_param) {
+        case OPERATOR:
+            copy_address(context->address, msg->parameter, sizeof(context->address));
+            context->next_param = TOKEN_ID;
+            break;
+        case TOKEN_ID:
+            copy_parameter(context->tokenId, msg->parameter, sizeof(context->tokenId));
+            context->next_param = NONE;
+            break;
+        default:
+            PRINTF("Unhandled parameter offset\n");
+            msg->result = ETH_PLUGIN_RESULT_ERROR;
+            break;
+    }
+}
+
+// `strict` will set msg->result to ERROR if parsing continues after `TOKEN_ID` has been parsed.
+static void handle_transfer(ethPluginProvideParameter_t *msg,
+                            erc721_context_t *context,
+                            bool strict) {
+    switch (context->next_param) {
+        case FROM:
+            context->next_param = TO;
+            break;
+        case TO:
+            copy_address(context->address, msg->parameter, sizeof(context->address));
+            context->next_param = TOKEN_ID;
+            break;
+        case TOKEN_ID:
+            copy_parameter(context->tokenId, msg->parameter, sizeof(context->tokenId));
+            context->next_param = NONE;
+            break;
+        default:
+            if (strict) {
+                PRINTF("Param %d not supported\n", context->next_param);
+                msg->result = ETH_PLUGIN_RESULT_ERROR;
+            }
+            break;
+    }
+}
+
+static void handle_approval_for_all(ethPluginProvideParameter_t *msg, erc721_context_t *context) {
+    switch (context->next_param) {
+        case OPERATOR:
+            context->next_param = APPROVED;
+            copy_address(context->address, msg->parameter, sizeof(context->address));
+            break;
+        case APPROVED:
+            context->next_param = NONE;
+            context->approved = msg->parameter[PARAMETER_LENGTH - 1];
+            break;
+        default:
+            PRINTF("Param %d not supported\n", context->next_param);
+            msg->result = ETH_PLUGIN_RESULT_ERROR;
+            break;
+    }
+}
+
+void handle_provide_parameter_721(void *parameters) {
+    ethPluginProvideParameter_t *msg = (ethPluginProvideParameter_t *) parameters;
+    erc721_context_t *context = (erc721_context_t *) msg->pluginContext;
+
+    PRINTF("erc721 plugin provide parameter %d %.*H\n",
+           msg->parameterOffset,
+           PARAMETER_LENGTH,
+           msg->parameter);
+
+    msg->result = ETH_PLUGIN_RESULT_SUCCESSFUL;
+    switch (context->selectorIndex) {
+        case APPROVE:
+            handle_approve(msg, context);
+            break;
+        case SAFE_TRANSFER:
+        case TRANSFER:
+            handle_transfer(msg, context, true);
+            break;
+        case SAFE_TRANSFER_DATA:
+            // Set `strict` to `false` because additional data might be present.
+            handle_transfer(msg, context, false);
+            break;
+        case SET_APPROVAL_FOR_ALL:
+            handle_approval_for_all(msg, context);
+            break;
+        default:
+            PRINTF("Selector index %d not supported\n", context->selectorIndex);
+            msg->result = ETH_PLUGIN_RESULT_ERROR;
+            break;
+    }
+}
+
+#endif  // HAVE_NFT_SUPPORT
diff --git a/src_plugins/erc721/erc721_ui.c b/src_plugins/erc721/erc721_ui.c
new file mode 100644
index 000000000..e312932b4
--- /dev/null
+++ b/src_plugins/erc721/erc721_ui.c
@@ -0,0 +1,170 @@
+#ifdef HAVE_NFT_SUPPORT
+
+#include "erc721_plugin.h"
+
+static void set_approval_ui(ethQueryContractUI_t *msg, erc721_context_t *context) {
+    switch (msg->screenIndex) {
+        case 0:
+            strlcpy(msg->title, "Allow", msg->titleLength);
+            getEthDisplayableAddress(context->address,
+                                     msg->msg,
+                                     msg->msgLength,
+                                     &global_sha3,
+                                     chainConfig->chainId);
+            break;
+        case 1:
+            strlcpy(msg->title, "To Spend Your", msg->titleLength);
+            if (msg->item1) {
+                strlcpy(msg->msg, (const char *) &msg->item1->nft.collectionName, msg->msgLength);
+            } else {
+                strlcpy(msg->msg, "Not found", msg->msgLength);
+            }
+            break;
+        case 2:
+            strlcpy(msg->title, "NFT Address", msg->titleLength);
+            getEthDisplayableAddress(msg->pluginSharedRO->txContent->destination,
+                                     msg->msg,
+                                     msg->msgLength,
+                                     &global_sha3,
+                                     chainConfig->chainId);
+            break;
+        case 3:
+            strlcpy(msg->title, "NFT ID", msg->titleLength);
+            uint256_to_decimal(context->tokenId,
+                               sizeof(context->tokenId),
+                               msg->msg,
+                               msg->msgLength);
+            break;
+        case 4:
+            strlcpy(msg->title, "And send", msg->titleLength);
+            amountToString((uint8_t *) &msg->pluginSharedRO->txContent->value,
+                           sizeof(msg->pluginSharedRO->txContent->value),
+                           WEI_TO_ETHER,
+                           msg->network_ticker,
+                           msg->msg,
+                           msg->msgLength);
+        default:
+            PRINTF("Unsupported screen index %d\n", msg->screenIndex);
+            msg->result = ETH_PLUGIN_RESULT_ERROR;
+            break;
+    }
+}
+
+static void set_approval_for_all_ui(ethQueryContractUI_t *msg, erc721_context_t *context) {
+    switch (msg->screenIndex) {
+        case 0:
+            if (context->approved) {
+                strlcpy(msg->title, "Allow", msg->titleLength);
+            } else {
+                strlcpy(msg->title, "Revoke", msg->titleLength);
+            }
+            getEthDisplayableAddress(context->address,
+                                     msg->msg,
+                                     msg->msgLength,
+                                     &global_sha3,
+                                     chainConfig->chainId);
+            break;
+        case 1:
+            strlcpy(msg->title, "To Manage ALL", msg->titleLength);
+            if (msg->item1) {
+                strlcpy(msg->msg, (const char *) &msg->item1->nft.collectionName, msg->msgLength);
+            } else {
+                strlcpy(msg->msg, "Not found", msg->msgLength);
+            }
+            break;
+        case 2:
+            strlcpy(msg->title, "NFT Address", msg->titleLength);
+            getEthDisplayableAddress(msg->pluginSharedRO->txContent->destination,
+                                     msg->msg,
+                                     msg->msgLength,
+                                     &global_sha3,
+                                     chainConfig->chainId);
+            break;
+        case 3:
+            strlcpy(msg->title, "And send", msg->titleLength);
+            amountToString((uint8_t *) &msg->pluginSharedRO->txContent->value,
+                           sizeof(msg->pluginSharedRO->txContent->value),
+                           WEI_TO_ETHER,
+                           msg->network_ticker,
+                           msg->msg,
+                           msg->msgLength);
+        default:
+            PRINTF("Unsupported screen index %d\n", msg->screenIndex);
+            msg->result = ETH_PLUGIN_RESULT_ERROR;
+            break;
+    }
+}
+
+static void set_transfer_ui(ethQueryContractUI_t *msg, erc721_context_t *context) {
+    switch (msg->screenIndex) {
+        case 0:
+            strlcpy(msg->title, "To", msg->titleLength);
+            getEthDisplayableAddress(context->address,
+                                     msg->msg,
+                                     msg->msgLength,
+                                     &global_sha3,
+                                     chainConfig->chainId);
+            break;
+        case 1:
+            strlcpy(msg->title, "Collection Name", msg->titleLength);
+            if (msg->item1) {
+                strlcpy(msg->msg, (const char *) &msg->item1->nft.collectionName, msg->msgLength);
+            } else {
+                strlcpy(msg->msg, "Not Found", msg->msgLength);
+            }
+            break;
+        case 2:
+            strlcpy(msg->title, "NFT Address", msg->titleLength);
+            getEthDisplayableAddress((uint8_t *) msg->item1->nft.contractAddress,
+                                     msg->msg,
+                                     msg->msgLength,
+                                     &global_sha3,
+                                     chainConfig->chainId);
+            break;
+        case 3:
+            strlcpy(msg->title, "NFT ID", msg->titleLength);
+            uint256_to_decimal(context->tokenId,
+                               sizeof(context->tokenId),
+                               msg->msg,
+                               msg->msgLength);
+            break;
+        case 4:
+            strlcpy(msg->title, "And send", msg->titleLength);
+            amountToString((uint8_t *) &msg->pluginSharedRO->txContent->value,
+                           sizeof(msg->pluginSharedRO->txContent->value),
+                           WEI_TO_ETHER,
+                           msg->network_ticker,
+                           msg->msg,
+                           msg->msgLength);
+        default:
+            PRINTF("Unsupported screen index %d\n", msg->screenIndex);
+            msg->result = ETH_PLUGIN_RESULT_ERROR;
+            break;
+    }
+}
+
+void handle_query_contract_ui_721(void *parameters) {
+    ethQueryContractUI_t *msg = (ethQueryContractUI_t *) parameters;
+    erc721_context_t *context = (erc721_context_t *) msg->pluginContext;
+
+    msg->result = ETH_PLUGIN_RESULT_OK;
+    switch (context->selectorIndex) {
+        case APPROVE:
+            set_approval_ui(msg, context);
+            break;
+        case SET_APPROVAL_FOR_ALL:
+            set_approval_for_all_ui(msg, context);
+            break;
+        case SAFE_TRANSFER_DATA:
+        case SAFE_TRANSFER:
+        case TRANSFER:
+            set_transfer_ui(msg, context);
+            break;
+        default:
+            msg->result = ETH_PLUGIN_RESULT_ERROR;
+            PRINTF("Unsupported selector index %d\n", context->selectorIndex);
+            break;
+    }
+}
+
+#endif  // HAVE_NFT_SUPPORT
diff --git a/src_plugins/starkware/starkware_plugin.c b/src_plugins/starkware/starkware_plugin.c
index 31abfb51e..f4ba5f1e3 100644
--- a/src_plugins/starkware/starkware_plugin.c
+++ b/src_plugins/starkware/starkware_plugin.c
@@ -188,8 +188,9 @@ bool starkware_verify_asset_id(uint8_t *tmp32, uint8_t *tokenId, bool assetTypeO
     if (quantumSet) {
         cx_sha3_t sha3;
         tokenDefinition_t *currentToken = NULL;
-        if (dataContext.tokenContext.quantumIndex != MAX_TOKEN) {
-            currentToken = &tmpCtx.transactionContext.tokens[dataContext.tokenContext.quantumIndex];
+        if (dataContext.tokenContext.quantumIndex != MAX_ITEMS) {
+            currentToken =
+                &tmpCtx.transactionContext.extraInfo[dataContext.tokenContext.quantumIndex].token;
         }
         cx_keccak_init(&sha3, 256);
         compute_token_id(&sha3,
@@ -214,9 +215,9 @@ bool starkware_verify_asset_id(uint8_t *tmp32, uint8_t *tokenId, bool assetTypeO
 
 bool starkware_verify_token(uint8_t *token) {
     if (quantumSet) {
-        if (dataContext.tokenContext.quantumIndex != MAX_TOKEN) {
+        if (dataContext.tokenContext.quantumIndex != MAX_ITEMS) {
             tokenDefinition_t *currentToken =
-                &tmpCtx.transactionContext.tokens[dataContext.tokenContext.quantumIndex];
+                &tmpCtx.transactionContext.extraInfo[dataContext.tokenContext.quantumIndex].token;
             if (memcmp(token + 32 - 20, currentToken->address, 20) != 0) {
                 PRINTF("Token not matching got %.*H\n", 20, token + 32 - 20);
                 PRINTF("Current token %.*H\n", 20, currentToken->address);
@@ -235,7 +236,7 @@ bool starkware_verify_token(uint8_t *token) {
 
 bool starkware_verify_quantum(uint8_t *quantum) {
     if (quantumSet) {
-        if (dataContext.tokenContext.quantumIndex != MAX_TOKEN) {
+        if (dataContext.tokenContext.quantumIndex != MAX_ITEMS) {
             if (memcmp(quantum, dataContext.tokenContext.quantum, 32) != 0) {
                 PRINTF("Quantum not matching got %.*H\n", 32, quantum);
                 PRINTF("Current quantum %.*H\n", 32, dataContext.tokenContext.quantum);
@@ -301,7 +302,7 @@ void starkware_print_amount(uint8_t *amountData,
     char *ticker = chainConfig->coinName;
 
     if ((amountData == NULL) ||
-        (forEscape && (dataContext.tokenContext.quantumIndex == MAX_TOKEN))) {
+        (forEscape && (dataContext.tokenContext.quantumIndex == MAX_ITEMS))) {
         decimals = WEI_TO_ETHER;
         if (!forEscape) {
             convertUint256BE(tmpContent.txContent.value.value,
@@ -312,7 +313,7 @@ void starkware_print_amount(uint8_t *amountData,
         }
     } else {
         tokenDefinition_t *token =
-            &tmpCtx.transactionContext.tokens[dataContext.tokenContext.quantumIndex];
+            &tmpCtx.transactionContext.extraInfo[dataContext.tokenContext.quantumIndex].token;
         decimals = token->decimals;
         ticker = token->ticker;
         readu256BE(amountData, &amountPre);
@@ -334,9 +335,9 @@ void starkware_print_amount(uint8_t *amountData,
 void starkware_print_ticker(char *destination, size_t destinationLength) {
     char *ticker = chainConfig->coinName;
 
-    if (dataContext.tokenContext.quantumIndex != MAX_TOKEN) {
+    if (dataContext.tokenContext.quantumIndex != MAX_ITEMS) {
         tokenDefinition_t *token =
-            &tmpCtx.transactionContext.tokens[dataContext.tokenContext.quantumIndex];
+            &tmpCtx.transactionContext.extraInfo[dataContext.tokenContext.quantumIndex].token;
         ticker = token->ticker;
     }
     strlcpy(destination, ticker, destinationLength);
@@ -345,9 +346,9 @@ void starkware_print_ticker(char *destination, size_t destinationLength) {
 // TODO : rewrite as independant code
 void starkware_print_asset_contract(char *destination, size_t destinationLength) {
     // token has been validated to be present previously
-    if (dataContext.tokenContext.quantumIndex != MAX_TOKEN) {
+    if (dataContext.tokenContext.quantumIndex != MAX_ITEMS) {
         tokenDefinition_t *token =
-            &tmpCtx.transactionContext.tokens[dataContext.tokenContext.quantumIndex];
+            &tmpCtx.transactionContext.extraInfo[dataContext.tokenContext.quantumIndex].token;
         getEthDisplayableAddress(token->address,
                                  destination,
                                  destinationLength,


Test patch:
diff --git a/tests/build_local_test_elfs.sh b/tests/build_local_test_elfs.sh
index dd84b457d..ab9a264e1 100755
--- a/tests/build_local_test_elfs.sh
+++ b/tests/build_local_test_elfs.sh
@@ -1,34 +1,44 @@
-#!/bin/bash
+#!/usr/bin/env bash
+
+TESTS_FULL_PATH=$(dirname "$(realpath "$0")")
 
 # FILL THESE WITH YOUR OWN SDKs PATHS
 # NANOS_SDK=
 # NANOX_SDK=
 
 # list of apps required by tests that we want to build here
-appnames=("ethereum" "ethereum_classic")
+APPS=("ethereum" "ethereum_classic")
+
+# list of SDKS
+NANO_SDKS=("$NANOS_SDK" "$NANOX_SDK")
+# list of target elf file name suffix
+FILE_SUFFIXES=("nanos" "nanox")
+
+# move to the tests directory
+cd "$TESTS_FULL_PATH" || exit 1
+
+# Do it only now since before the cd command, we might not have been inside the repository
+GIT_REPO_ROOT=$(git rev-parse --show-toplevel)
+TESTS_REL_PATH=$(realpath --relative-to="$GIT_REPO_ROOT" "$TESTS_FULL_PATH")
 
-# create elfs folder if it doesn't exist
+# create elfs directory if it doesn't exist
 mkdir -p elfs
 
 # move to repo's root to build apps
-cd ..
-
-echo "*Building elfs for Nano S..."
-for app in "${appnames[@]}"
-do
-   echo "**Building $app for Nano S..."
-   make clean BOLOS_SDK=$NANOS_SDK
-   make -j DEBUG=1 ALLOW_DATA=1 BOLOS_SDK=$NANOS_SDK CHAIN=$app
-   cp bin/app.elf "tests/elfs/${app}_nanos.elf"
-done
+cd "$GIT_REPO_ROOT" || exit 1
 
-echo "*Building elfs for Nano X..."
-for app in "${appnames[@]}"
+for ((sdk_idx=0; sdk_idx < "${#NANO_SDKS[@]}"; sdk_idx++))
 do
-   echo "**Building $app for Nano X..."
-   make clean BOLOS_SDK=$NANOX_SDK
-   make -j DEBUG=1 ALLOW_DATA=1 BOLOS_SDK=$NANOX_SDK CHAIN=$app
-   cp bin/app.elf "tests/elfs/${app}_nanox.elf"
+    nano_sdk="${NANO_SDKS[$sdk_idx]}"
+    elf_suffix="${FILE_SUFFIXES[$sdk_idx]}"
+    echo "* Building elfs for $(basename "$nano_sdk")..."
+    for appname in "${APPS[@]}"
+    do
+       echo "** Building app $appname..."
+       make clean BOLOS_SDK="$nano_sdk"
+       make -j DEBUG=1 NFT_TESTING_KEY=1 BOLOS_SDK="$nano_sdk" CHAIN="$appname"
+       cp bin/app.elf "$TESTS_REL_PATH/elfs/${appname}_${elf_suffix}.elf"
+    done
 done
 
 echo "done"
diff --git a/tests/package.json b/tests/package.json
index ffc981508..98b51ae70 100644
--- a/tests/package.json
+++ b/tests/package.json
@@ -20,6 +20,7 @@
     "bip32-path": "^0.4.2",
     "core-js": "^3.7.0",
     "ethereum-tx-decoder": "^3.0.0",
+    "ethers": "^5.5.1",
     "fs-extra": "^10.0.0",
     "google-protobuf": "^3.11.0",
     "jest-serial-runner": "^1.1.0",
diff --git a/tests/snapshots/nanos_enable_blind_signing/00000.png b/tests/snapshots/nanos_enable_blind_signing/00000.png
new file mode 100644
index 000000000..0bef4f3a7
Binary files /dev/null and b/tests/snapshots/nanos_enable_blind_signing/00000.png differ
diff --git a/tests/snapshots/nanos_enable_blind_signing/00001.png b/tests/snapshots/nanos_enable_blind_signing/00001.png
new file mode 100644
index 000000000..29a6bd332
Binary files /dev/null and b/tests/snapshots/nanos_enable_blind_signing/00001.png differ
diff --git a/tests/snapshots/nanos_enable_blind_signing/00002.png b/tests/snapshots/nanos_enable_blind_signing/00002.png
new file mode 100644
index 000000000..d4bd8ad27
Binary files /dev/null and b/tests/snapshots/nanos_enable_blind_signing/00002.png differ
diff --git a/tests/snapshots/nanos_enable_blind_signing/00003.png b/tests/snapshots/nanos_enable_blind_signing/00003.png
new file mode 100644
index 000000000..e4ad84fa4
Binary files /dev/null and b/tests/snapshots/nanos_enable_blind_signing/00003.png differ
diff --git a/tests/snapshots/nanos_enable_blind_signing/00004.png b/tests/snapshots/nanos_enable_blind_signing/00004.png
new file mode 100644
index 000000000..7b3e0eabd
Binary files /dev/null and b/tests/snapshots/nanos_enable_blind_signing/00004.png differ
diff --git a/tests/snapshots/nanos_enable_blind_signing/00005.png b/tests/snapshots/nanos_enable_blind_signing/00005.png
new file mode 100644
index 000000000..28e70e8bc
Binary files /dev/null and b/tests/snapshots/nanos_enable_blind_signing/00005.png differ
diff --git a/tests/snapshots/nanos_enable_blind_signing/00006.png b/tests/snapshots/nanos_enable_blind_signing/00006.png
new file mode 100644
index 000000000..c844a37fb
Binary files /dev/null and b/tests/snapshots/nanos_enable_blind_signing/00006.png differ
diff --git a/tests/snapshots/nanos_enable_blind_signing/00007.png b/tests/snapshots/nanos_enable_blind_signing/00007.png
new file mode 100644
index 000000000..be028387e
Binary files /dev/null and b/tests/snapshots/nanos_enable_blind_signing/00007.png differ
diff --git a/tests/snapshots/nanos_enable_blind_signing/00008.png b/tests/snapshots/nanos_enable_blind_signing/00008.png
new file mode 100644
index 000000000..0bef4f3a7
Binary files /dev/null and b/tests/snapshots/nanos_enable_blind_signing/00008.png differ
diff --git a/tests/snapshots/nanos_erc721_approval_for_all/00000.png b/tests/snapshots/nanos_erc721_approval_for_all/00000.png
new file mode 100644
index 000000000..2994983f8
Binary files /dev/null and b/tests/snapshots/nanos_erc721_approval_for_all/00000.png differ
diff --git a/tests/snapshots/nanos_erc721_approval_for_all/00001.png b/tests/snapshots/nanos_erc721_approval_for_all/00001.png
new file mode 100644
index 000000000..ef35bb6b0
Binary files /dev/null and b/tests/snapshots/nanos_erc721_approval_for_all/00001.png differ
diff --git a/tests/snapshots/nanos_erc721_approval_for_all/00002.png b/tests/snapshots/nanos_erc721_approval_for_all/00002.png
new file mode 100644
index 000000000..32dd3f821
Binary files /dev/null and b/tests/snapshots/nanos_erc721_approval_for_all/00002.png differ
diff --git a/tests/snapshots/nanos_erc721_approval_for_all/00003.png b/tests/snapshots/nanos_erc721_approval_for_all/00003.png
new file mode 100644
index 000000000..c8a38aeb8
Binary files /dev/null and b/tests/snapshots/nanos_erc721_approval_for_all/00003.png differ
diff --git a/tests/snapshots/nanos_erc721_approval_for_all/00004.png b/tests/snapshots/nanos_erc721_approval_for_all/00004.png
new file mode 100644
index 000000000..c6051dd16
Binary files /dev/null and b/tests/snapshots/nanos_erc721_approval_for_all/00004.png differ
diff --git a/tests/snapshots/nanos_erc721_approval_for_all/00005.png b/tests/snapshots/nanos_erc721_approval_for_all/00005.png
new file mode 100644
index 000000000..653e23c99
Binary files /dev/null and b/tests/snapshots/nanos_erc721_approval_for_all/00005.png differ
diff --git a/tests/snapshots/nanos_erc721_approval_for_all/00006.png b/tests/snapshots/nanos_erc721_approval_for_all/00006.png
new file mode 100644
index 000000000..b5efb2146
Binary files /dev/null and b/tests/snapshots/nanos_erc721_approval_for_all/00006.png differ
diff --git a/tests/snapshots/nanos_erc721_approval_for_all/00007.png b/tests/snapshots/nanos_erc721_approval_for_all/00007.png
new file mode 100644
index 000000000..bf629b421
Binary files /dev/null and b/tests/snapshots/nanos_erc721_approval_for_all/00007.png differ
diff --git a/tests/snapshots/nanos_erc721_approval_for_all/00008.png b/tests/snapshots/nanos_erc721_approval_for_all/00008.png
new file mode 100644
index 000000000..6e52ae4f8
Binary files /dev/null and b/tests/snapshots/nanos_erc721_approval_for_all/00008.png differ
diff --git a/tests/snapshots/nanos_erc721_approval_for_all/00009.png b/tests/snapshots/nanos_erc721_approval_for_all/00009.png
new file mode 100644
index 000000000..11c298f50
Binary files /dev/null and b/tests/snapshots/nanos_erc721_approval_for_all/00009.png differ
diff --git a/tests/snapshots/nanos_erc721_approval_for_all/00010.png b/tests/snapshots/nanos_erc721_approval_for_all/00010.png
new file mode 100644
index 000000000..595591af3
Binary files /dev/null and b/tests/snapshots/nanos_erc721_approval_for_all/00010.png differ
diff --git a/tests/snapshots/nanos_erc721_approval_for_all/00011.png b/tests/snapshots/nanos_erc721_approval_for_all/00011.png
new file mode 100644
index 000000000..c3bc4f72f
Binary files /dev/null and b/tests/snapshots/nanos_erc721_approval_for_all/00011.png differ
diff --git a/tests/snapshots/nanos_erc721_approval_for_all/00012.png b/tests/snapshots/nanos_erc721_approval_for_all/00012.png
new file mode 100644
index 000000000..3158ea690
Binary files /dev/null and b/tests/snapshots/nanos_erc721_approval_for_all/00012.png differ
diff --git a/tests/snapshots/nanos_erc721_approval_for_all/00013.png b/tests/snapshots/nanos_erc721_approval_for_all/00013.png
new file mode 100644
index 000000000..0bef4f3a7
Binary files /dev/null and b/tests/snapshots/nanos_erc721_approval_for_all/00013.png differ
diff --git a/tests/snapshots/nanos_erc721_safe_transfer/00000.png b/tests/snapshots/nanos_erc721_safe_transfer/00000.png
new file mode 100644
index 000000000..2994983f8
Binary files /dev/null and b/tests/snapshots/nanos_erc721_safe_transfer/00000.png differ
diff --git a/tests/snapshots/nanos_erc721_safe_transfer/00001.png b/tests/snapshots/nanos_erc721_safe_transfer/00001.png
new file mode 100644
index 000000000..6ff345b74
Binary files /dev/null and b/tests/snapshots/nanos_erc721_safe_transfer/00001.png differ
diff --git a/tests/snapshots/nanos_erc721_safe_transfer/00002.png b/tests/snapshots/nanos_erc721_safe_transfer/00002.png
new file mode 100644
index 000000000..795947ce2
Binary files /dev/null and b/tests/snapshots/nanos_erc721_safe_transfer/00002.png differ
diff --git a/tests/snapshots/nanos_erc721_safe_transfer/00003.png b/tests/snapshots/nanos_erc721_safe_transfer/00003.png
new file mode 100644
index 000000000..ebd34d4d7
Binary files /dev/null and b/tests/snapshots/nanos_erc721_safe_transfer/00003.png differ
diff --git a/tests/snapshots/nanos_erc721_safe_transfer/00004.png b/tests/snapshots/nanos_erc721_safe_transfer/00004.png
new file mode 100644
index 000000000..de1b2bc7d
Binary files /dev/null and b/tests/snapshots/nanos_erc721_safe_transfer/00004.png differ
diff --git a/tests/snapshots/nanos_erc721_safe_transfer/00005.png b/tests/snapshots/nanos_erc721_safe_transfer/00005.png
new file mode 100644
index 000000000..5338822b5
Binary files /dev/null and b/tests/snapshots/nanos_erc721_safe_transfer/00005.png differ
diff --git a/tests/snapshots/nanos_erc721_safe_transfer/00006.png b/tests/snapshots/nanos_erc721_safe_transfer/00006.png
new file mode 100644
index 000000000..b5efb2146
Binary files /dev/null and b/tests/snapshots/nanos_erc721_safe_transfer/00006.png differ
diff --git a/tests/snapshots/nanos_erc721_safe_transfer/00007.png b/tests/snapshots/nanos_erc721_safe_transfer/00007.png
new file mode 100644
index 000000000..bf629b421
Binary files /dev/null and b/tests/snapshots/nanos_erc721_safe_transfer/00007.png differ
diff --git a/tests/snapshots/nanos_erc721_safe_transfer/00008.png b/tests/snapshots/nanos_erc721_safe_transfer/00008.png
new file mode 100644
index 000000000..6e52ae4f8
Binary files /dev/null and b/tests/snapshots/nanos_erc721_safe_transfer/00008.png differ
diff --git a/tests/snapshots/nanos_erc721_safe_transfer/00009.png b/tests/snapshots/nanos_erc721_safe_transfer/00009.png
new file mode 100644
index 000000000..831002983
Binary files /dev/null and b/tests/snapshots/nanos_erc721_safe_transfer/00009.png differ
diff --git a/tests/snapshots/nanos_erc721_safe_transfer/00010.png b/tests/snapshots/nanos_erc721_safe_transfer/00010.png
new file mode 100644
index 000000000..3158ea690
Binary files /dev/null and b/tests/snapshots/nanos_erc721_safe_transfer/00010.png differ
diff --git a/tests/snapshots/nanos_erc721_safe_transfer/00011.png b/tests/snapshots/nanos_erc721_safe_transfer/00011.png
new file mode 100644
index 000000000..0bef4f3a7
Binary files /dev/null and b/tests/snapshots/nanos_erc721_safe_transfer/00011.png differ
diff --git a/tests/snapshots/nanos_erc721_transfer/00000.png b/tests/snapshots/nanos_erc721_transfer/00000.png
new file mode 100644
index 000000000..2994983f8
Binary files /dev/null and b/tests/snapshots/nanos_erc721_transfer/00000.png differ
diff --git a/tests/snapshots/nanos_erc721_transfer/00001.png b/tests/snapshots/nanos_erc721_transfer/00001.png
new file mode 100644
index 000000000..6ff345b74
Binary files /dev/null and b/tests/snapshots/nanos_erc721_transfer/00001.png differ
diff --git a/tests/snapshots/nanos_erc721_transfer/00002.png b/tests/snapshots/nanos_erc721_transfer/00002.png
new file mode 100644
index 000000000..36ec626b4
Binary files /dev/null and b/tests/snapshots/nanos_erc721_transfer/00002.png differ
diff --git a/tests/snapshots/nanos_erc721_transfer/00003.png b/tests/snapshots/nanos_erc721_transfer/00003.png
new file mode 100644
index 000000000..b656cdff3
Binary files /dev/null and b/tests/snapshots/nanos_erc721_transfer/00003.png differ
diff --git a/tests/snapshots/nanos_erc721_transfer/00004.png b/tests/snapshots/nanos_erc721_transfer/00004.png
new file mode 100644
index 000000000..250de7046
Binary files /dev/null and b/tests/snapshots/nanos_erc721_transfer/00004.png differ
diff --git a/tests/snapshots/nanos_erc721_transfer/00005.png b/tests/snapshots/nanos_erc721_transfer/00005.png
new file mode 100644
index 000000000..2caf5074b
Binary files /dev/null and b/tests/snapshots/nanos_erc721_transfer/00005.png differ
diff --git a/tests/snapshots/nanos_erc721_transfer/00006.png b/tests/snapshots/nanos_erc721_transfer/00006.png
new file mode 100644
index 000000000..f7676d018
Binary files /dev/null and b/tests/snapshots/nanos_erc721_transfer/00006.png differ
diff --git a/tests/snapshots/nanos_erc721_transfer/00007.png b/tests/snapshots/nanos_erc721_transfer/00007.png
new file mode 100644
index 000000000..9eeff67dd
Binary files /dev/null and b/tests/snapshots/nanos_erc721_transfer/00007.png differ
diff --git a/tests/snapshots/nanos_erc721_transfer/00008.png b/tests/snapshots/nanos_erc721_transfer/00008.png
new file mode 100644
index 000000000..c830a35ae
Binary files /dev/null and b/tests/snapshots/nanos_erc721_transfer/00008.png differ
diff --git a/tests/snapshots/nanos_erc721_transfer/00009.png b/tests/snapshots/nanos_erc721_transfer/00009.png
new file mode 100644
index 000000000..11c298f50
Binary files /dev/null and b/tests/snapshots/nanos_erc721_transfer/00009.png differ
diff --git a/tests/snapshots/nanos_erc721_transfer/00010.png b/tests/snapshots/nanos_erc721_transfer/00010.png
new file mode 100644
index 000000000..0a287c791
Binary files /dev/null and b/tests/snapshots/nanos_erc721_transfer/00010.png differ
diff --git a/tests/snapshots/nanos_erc721_transfer/00011.png b/tests/snapshots/nanos_erc721_transfer/00011.png
new file mode 100644
index 000000000..886743739
Binary files /dev/null and b/tests/snapshots/nanos_erc721_transfer/00011.png differ
diff --git a/tests/snapshots/nanos_erc721_transfer/00012.png b/tests/snapshots/nanos_erc721_transfer/00012.png
new file mode 100644
index 000000000..3158ea690
Binary files /dev/null and b/tests/snapshots/nanos_erc721_transfer/00012.png differ
diff --git a/tests/snapshots/nanos_erc721_transfer/00013.png b/tests/snapshots/nanos_erc721_transfer/00013.png
new file mode 100644
index 000000000..0bef4f3a7
Binary files /dev/null and b/tests/snapshots/nanos_erc721_transfer/00013.png differ
diff --git a/tests/snapshots/nanos_erc721_transfer_with_eth/00000.png b/tests/snapshots/nanos_erc721_transfer_with_eth/00000.png
new file mode 100644
index 000000000..2994983f8
Binary files /dev/null and b/tests/snapshots/nanos_erc721_transfer_with_eth/00000.png differ
diff --git a/tests/snapshots/nanos_erc721_transfer_with_eth/00001.png b/tests/snapshots/nanos_erc721_transfer_with_eth/00001.png
new file mode 100644
index 000000000..6ff345b74
Binary files /dev/null and b/tests/snapshots/nanos_erc721_transfer_with_eth/00001.png differ
diff --git a/tests/snapshots/nanos_erc721_transfer_with_eth/00002.png b/tests/snapshots/nanos_erc721_transfer_with_eth/00002.png
new file mode 100644
index 000000000..36ec626b4
Binary files /dev/null and b/tests/snapshots/nanos_erc721_transfer_with_eth/00002.png differ
diff --git a/tests/snapshots/nanos_erc721_transfer_with_eth/00003.png b/tests/snapshots/nanos_erc721_transfer_with_eth/00003.png
new file mode 100644
index 000000000..b656cdff3
Binary files /dev/null and b/tests/snapshots/nanos_erc721_transfer_with_eth/00003.png differ
diff --git a/tests/snapshots/nanos_erc721_transfer_with_eth/00004.png b/tests/snapshots/nanos_erc721_transfer_with_eth/00004.png
new file mode 100644
index 000000000..250de7046
Binary files /dev/null and b/tests/snapshots/nanos_erc721_transfer_with_eth/00004.png differ
diff --git a/tests/snapshots/nanos_erc721_transfer_with_eth/00005.png b/tests/snapshots/nanos_erc721_transfer_with_eth/00005.png
new file mode 100644
index 000000000..2caf5074b
Binary files /dev/null and b/tests/snapshots/nanos_erc721_transfer_with_eth/00005.png differ
diff --git a/tests/snapshots/nanos_erc721_transfer_with_eth/00006.png b/tests/snapshots/nanos_erc721_transfer_with_eth/00006.png
new file mode 100644
index 000000000..f7676d018
Binary files /dev/null and b/tests/snapshots/nanos_erc721_transfer_with_eth/00006.png differ
diff --git a/tests/snapshots/nanos_erc721_transfer_with_eth/00007.png b/tests/snapshots/nanos_erc721_transfer_with_eth/00007.png
new file mode 100644
index 000000000..9eeff67dd
Binary files /dev/null and b/tests/snapshots/nanos_erc721_transfer_with_eth/00007.png differ
diff --git a/tests/snapshots/nanos_erc721_transfer_with_eth/00008.png b/tests/snapshots/nanos_erc721_transfer_with_eth/00008.png
new file mode 100644
index 000000000..c830a35ae
Binary files /dev/null and b/tests/snapshots/nanos_erc721_transfer_with_eth/00008.png differ
diff --git a/tests/snapshots/nanos_erc721_transfer_with_eth/00009.png b/tests/snapshots/nanos_erc721_transfer_with_eth/00009.png
new file mode 100644
index 000000000..84d25eba8
Binary files /dev/null and b/tests/snapshots/nanos_erc721_transfer_with_eth/00009.png differ
diff --git a/tests/snapshots/nanos_erc721_transfer_with_eth/00010.png b/tests/snapshots/nanos_erc721_transfer_with_eth/00010.png
new file mode 100644
index 000000000..11c298f50
Binary files /dev/null and b/tests/snapshots/nanos_erc721_transfer_with_eth/00010.png differ
diff --git a/tests/snapshots/nanos_erc721_transfer_with_eth/00011.png b/tests/snapshots/nanos_erc721_transfer_with_eth/00011.png
new file mode 100644
index 000000000..0a287c791
Binary files /dev/null and b/tests/snapshots/nanos_erc721_transfer_with_eth/00011.png differ
diff --git a/tests/snapshots/nanos_erc721_transfer_with_eth/00012.png b/tests/snapshots/nanos_erc721_transfer_with_eth/00012.png
new file mode 100644
index 000000000..886743739
Binary files /dev/null and b/tests/snapshots/nanos_erc721_transfer_with_eth/00012.png differ
diff --git a/tests/snapshots/nanos_erc721_transfer_with_eth/00013.png b/tests/snapshots/nanos_erc721_transfer_with_eth/00013.png
new file mode 100644
index 000000000..3158ea690
Binary files /dev/null and b/tests/snapshots/nanos_erc721_transfer_with_eth/00013.png differ
diff --git a/tests/snapshots/nanos_erc721_transfer_with_eth/00014.png b/tests/snapshots/nanos_erc721_transfer_with_eth/00014.png
new file mode 100644
index 000000000..0bef4f3a7
Binary files /dev/null and b/tests/snapshots/nanos_erc721_transfer_with_eth/00014.png differ
diff --git a/tests/src/approve.test.js b/tests/src/approve.test.js
index fea24a5f2..63496f5af 100644
--- a/tests/src/approve.test.js
+++ b/tests/src/approve.test.js
@@ -19,7 +19,7 @@ test('[Nano S] Approve DAI tokens', zemu("nanos", async (sim, eth) => {
   });
 }));
 
-test('[Nano X] Approve DAI tokens', zemu("nanox", async (sim, eth) => {
+test.skip('[Nano X] Approve DAI tokens', zemu("nanox", async (sim, eth) => {
 
   const tx = eth.signTransaction(
     "44'/60'/1'/0/0",
diff --git a/tests/src/blind_compound_deposit.test.js b/tests/src/blind_compound_deposit.test.js
index 537ed8d49..f38fe8eae 100644
--- a/tests/src/blind_compound_deposit.test.js
+++ b/tests/src/blind_compound_deposit.test.js
@@ -3,6 +3,8 @@ import "regenerator-runtime/runtime";
 import { waitForAppScreen, zemu } from './test.fixture';
 
 test('[Nano S] Deposit ETH on compound, blind sign', zemu("nanos", async (sim, eth) => {
+    // Enable blind-signing
+    await sim.navigateAndCompareSnapshots('.', 'nanos_enable_blind_signing', [-2, 0, 0, 3, 0]);
 
   const tx = eth.signTransaction(
     "44'/60'/1'/0/0",
@@ -19,7 +21,7 @@ test('[Nano S] Deposit ETH on compound, blind sign', zemu("nanos", async (sim, e
   });
 }));
 
-test('[Nano X] Deposit ETH on compound, blind sign', zemu("nanox", async (sim, eth) => {
+test.skip('[Nano X] Deposit ETH on compound, blind sign', zemu("nanox", async (sim, eth) => {
 
   const tx = eth.signTransaction(
     "44'/60'/1'/0/0",
diff --git a/tests/src/chainid.test.js b/tests/src/chainid.test.js
index 7461c83eb..10247fc89 100644
--- a/tests/src/chainid.test.js
+++ b/tests/src/chainid.test.js
@@ -36,7 +36,7 @@ test('[Nano S] Transfer on palm network on Ethereum', zemu("nanos", async (sim,
   });
 }));
 
-test('[Nano X] Transfer on network 112233445566 on Ethereum', zemu("nanox", async (sim, eth) => {
+test.skip('[Nano X] Transfer on network 112233445566 on Ethereum', zemu("nanox", async (sim, eth) => {
 
   const tx = eth.signTransaction(
     "44'/60'/1'/0/0",
@@ -53,7 +53,7 @@ test('[Nano X] Transfer on network 112233445566 on Ethereum', zemu("nanox", asyn
   });
 }));
 
-test('[Nano X] Transfer on palm network on Ethereum', zemu("nanox", async (sim, eth) => {
+test.skip('[Nano X] Transfer on palm network on Ethereum', zemu("nanox", async (sim, eth) => {
 
   const tx = eth.signTransaction(
     "44'/60'/1'/0/0",
@@ -68,4 +68,4 @@ test('[Nano X] Transfer on palm network on Ethereum', zemu("nanox", async (sim,
     "s": "3698e84564e58477a49f7a9cea572ef5d672a5538db08f3ee42df5eb75a1b907",
     "v": "0542b8613d",
   });
-}));
\ No newline at end of file
+}));
diff --git a/tests/src/contract_data_warning.test.js b/tests/src/contract_data_warning.test.js
index 2ebb16f66..49fb200f0 100644
--- a/tests/src/contract_data_warning.test.js
+++ b/tests/src/contract_data_warning.test.js
@@ -6,9 +6,6 @@ import { waitForAppScreen, zemu } from './test.fixture';
 import Zemu from '@zondax/zemu';
 
 test('[Nano S] Try to blind sign with setting disabled', zemu("nanos", async (sim, eth) => {
-  // disable blind signing
-  await sim.navigateAndCompareSnapshots('.', 'nanos_disable_blind_signing', [-2, 0, 0, 3, 0]);
-
   // we can't use eth.signTransaction because it detects that contract data is disabled and fails early
   let transport = await sim.getTransport();
   let buffer = Buffer.from("058000002c8000003c800000010000000000000000f849208506fc23ac008303dc3194f650c3d88d12db855b8bf7d11be6c55a4e07dcc980a4a1712d6800000000000000000000000000000000000000000000000000000000000acbc7018080", "hex");
@@ -21,7 +18,7 @@ test('[Nano S] Try to blind sign with setting disabled', zemu("nanos", async (si
   await sim.navigateAndCompareSnapshots('.', 'nanos_try_to_blind_sign_with_setting_disabled', [1, 0]);
 }));
 
-test('[Nano X] Try to blind sign with setting disabled', zemu("nanox", async (sim, eth) => {
+test.skip('[Nano X] Try to blind sign with setting disabled', zemu("nanox", async (sim, eth) => {
   // disable blind signing
   await sim.navigateAndCompareSnapshots('.', 'nanox_disable_blind_signing', [-2, 0, 0, 3, 0]);
 
@@ -35,4 +32,4 @@ test('[Nano X] Try to blind sign with setting disabled', zemu("nanox", async (si
   await Zemu.sleep(1000);
   await waitForAppScreen(sim);
   await sim.navigateAndCompareSnapshots('.', 'nanox_try_to_blind_sign_with_setting_disabled', [0]);
-}));
\ No newline at end of file
+}));
diff --git a/tests/src/eip1559.test.js b/tests/src/eip1559.test.js
index 10831c687..37c803768 100644
--- a/tests/src/eip1559.test.js
+++ b/tests/src/eip1559.test.js
@@ -20,7 +20,7 @@ test('[Nano S] Transfer eip1559', zemu("nanos", async (sim, eth) => {
 
 }));
 
-test('[Nano X] Transfer eip1559', zemu("nanox", async (sim, eth) => {
+test.skip('[Nano X] Transfer eip1559', zemu("nanox", async (sim, eth) => {
 
   const tx = eth.signTransaction(
     "44'/60'/0'/0/0",
@@ -36,4 +36,4 @@ test('[Nano X] Transfer eip1559', zemu("nanox", async (sim, eth) => {
     "v": "01"
   });
 
-}));
\ No newline at end of file
+}));
diff --git a/tests/src/erc1155.test.js b/tests/src/erc1155.test.js
new file mode 100644
index 000000000..59eeee02d
--- /dev/null
+++ b/tests/src/erc1155.test.js
@@ -0,0 +1,27 @@
+import "core-js/stable";
+import "regenerator-runtime/runtime";
+import { waitForAppScreen, zemu, txFromEtherscan } from './test.fixture';
+import { TransportStatusError } from "@ledgerhq/errors";
+
+// -------------------
+// TODO: Actually write the tests
+
+test.skip('[Nano S] Transfer 1155', zemu("nanos", async (sim, eth) => {
+
+  const rawTx  = ""
+  const serializedTx = txFromEtherscan(rawTx);
+
+  // with ETH need to test
+  // const serializedTx = txFromEtherscan("0x02f901350182022f8459682f0085246ad7eb3182de2994424db67b40b15ed85475c3f29dedf601b6ee75b283424242b8c4f242432a000000000000000000000000dcdb88f3754b2841093d9348a2d02df8cf06314c000000000000000000000000df9fb2eff1f2871caeeb94bf262ffba84efddddc0000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000c001a0c4283f86dc852e43e9fd1077b448c63fec76bdeb44dfac977730725e41fa3676a0543b2d2f99f65fb20cd548964eee94b1c1865919f4574c7089d8b95678b667c2");
+
+  const tx = eth.signTransaction(
+    "44'/60'/1'/0/0",
+    serializedTx,
+  );
+
+  await waitForAppScreen(sim);
+  await sim.navigateAndCompareSnapshots('.', 'nanos_erc721_transfer_ethereum', [12, 0]);
+
+  await expect(tx).resolves.toEqual({
+  });
+}));
diff --git a/tests/src/erc721.test.js b/tests/src/erc721.test.js
new file mode 100644
index 000000000..58052f142
--- /dev/null
+++ b/tests/src/erc721.test.js
@@ -0,0 +1,130 @@
+import "core-js/stable";
+import "regenerator-runtime/runtime";
+import { waitForAppScreen, zemu, txFromEtherscan } from './test.fixture';
+
+test.skip('[Nano S] Transfer erc721', zemu("nanos", async (sim, eth) => {
+
+  // https://etherscan.io/tx/0x73cec4fc07de3a24ba42e8756e13b7ddfa9bd449126c37640881195e8ea9e679
+  // Modified to put a bigger token id
+  const rawTx = "0x02f8d101058459682f0085233da9943e8301865b94bd3531da5cf5857e7cfaa92426877b022e612cf880b86423b872dd0000000000000000000000004cc568b73c0dcf8e90db26d7fd3a6cfadca108a3000000000000000000000000d4c9b20950c3eca38fc1f33f54bdf9694e488799ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc080a094c8632fe7277aa8c54cea9d81a15911cfa4970a2bf7356d14d04cc5afbcdab7a013a77b8c79e5d9b2b35edb3c44db3bb41b92f5c463ff126bf19d213b2b9ba8b5"
+  const serializedTx = txFromEtherscan(rawTx);
+
+  const tx = eth.signTransaction(
+    "44'/60'/1'/0/0",
+    serializedTx,
+  );
+
+  await waitForAppScreen(sim);
+  await sim.navigateAndCompareSnapshots('.', 'nanos_erc721_transfer', [12, 0]);
+
+  await expect(tx).resolves.toEqual({
+    "r": "59f6a9769cff66eed8be8716c44d39808d1e43f3aa0bb97538e124dba4bc4565",
+    "s": "662990a841c663a165ba9a83e5cc95c03a999b851e0bd6d296aa70a0f7c96c1a",
+    "v": "01",
+  });
+}));
+
+test.skip('[Nano S] Transfer erc721 with attached ETH', zemu("nanos", async (sim, eth) => {
+
+  const rawTx = "0x02f8d601058459682f0085233da9943e8301865b94bd3531da5cf5857e7cfaa92426877b022e612cf8854242424242b86423b872dd0000000000000000000000004cc568b73c0dcf8e90db26d7fd3a6cfadca108a3000000000000000000000000d4c9b20950c3eca38fc1f33f54bdf9694e4887990000000000000000000000000000000000000000000000000000000000000f21c080a094c8632fe7277aa8c54cea9d81a15911cfa4970a2bf7356d14d04cc5afbcdab7a013a77b8c79e5d9b2b35edb3c44db3bb41b92f5c463ff126bf19d213b2b9ba8b5"
+  const serializedTx = txFromEtherscan(rawTx);
+
+
+  const tx = eth.signTransaction(
+    "44'/60'/1'/0/0",
+    serializedTx,
+  );
+
+  await waitForAppScreen(sim);
+  await sim.navigateAndCompareSnapshots('.', 'nanos_erc721_transfer_with_eth', [13, 0]);
+
+  await expect(tx).resolves.toEqual({
+    "r": "9c42e10b49f3ee315ab2d5f7ad96f1068c75578734b66504716cc279ead27d47",
+    "s": "45dde78470ad75ffdb27a799b87e4934e2e10e98dbc6f88bc4a9bc19c4de86bc",
+    "v": "00",
+  });
+}));
+
+test.skip('[Nano S] set approval for all erc721', zemu("nanos", async (sim, eth) => {
+
+  // https://etherscan.io/tx/0x86b936db53c19fddf26b8d145f165e1c7fdff3c0f8b14b7758a38f0400cfd93f
+  const rawTx = "0x02f8b0010c8459682f00852cfbb00ee682b54294d4e4078ca3495de5b1d4db434bebc5a98619778280b844a22cb4650000000000000000000000002efcb1e8d4472d35356b9747bea8a051eac2e3f50000000000000000000000000000000000000000000000000000000000000001c001a0c5b8c024c15ca1452ce8a13eacfcdc25f1c6f581bb3ce570e82f08f1b792b3aca03be4dba0302ae190618a72eb1202ce3af3e17afd7d8a94345a48cae5cad15541";
+  const serializedTx = txFromEtherscan(rawTx);
+
+
+  const tx = eth.signTransaction(
+    "44'/60'/1'/0/0",
+    serializedTx,
+  );
+
+  await waitForAppScreen(sim);
+  await sim.navigateAndCompareSnapshots('.', 'nanos_erc721_approval_for_all', [12, 0]);
+
+  await expect(tx).resolves.toEqual({
+    "r": "8b6a70a1fe76d8e9b1250531a17eb1e367936732d4dfb9befc81a5031b271dc8",
+    "s": "7658d7151bba0d8504cea2013bead64cb8407dc6be1fca829bb9594b56f679af",
+    "v": "00",
+  });
+}));
+
+// NOT DONE
+test.skip('[Nano S] approval erc721', zemu("nanos", async (sim, eth) => {
+
+  // INCORRECT, need to find / create an approval tx
+  const rawTx = "";
+  const serializedTx = txFromEtherscan(rawTx);
+
+
+  const tx = eth.signTransaction(
+    "44'/60'/1'/0/0",
+    serializedTx,
+  );
+
+  await waitForAppScreen(sim);
+  await sim.navigateAndCompareSnapshots('.', 'nanos_erc721_approval', [12, 0]);
+
+  await expect(tx).resolves.toEqual({
+  });
+}));
+
+test.skip('[Nano S] safe transfer erc721', zemu("nanos", async (sim, eth) => {
+
+  // https://etherscan.io/tx/0x1ee6ce9be1c9fe6f030ff124ba8c88a410223c022816547e4b3fedd3a4d2dc1e
+  const rawTx = "0xf8cc82028585077359400083061a8094d4e4078ca3495de5b1d4db434bebc5a98619778280b86442842e0e000000000000000000000000c352b534e8b987e036a93539fd6897f53488e56a0000000000000000000000000a9287d9339c175cd3ea0ad4228f734a9f75ee6200000000000000000000000000000000000000000000000000000000000000621ca08250f4b2c8f28c5e4ef621dba4682990d1faf930c8cb6d032c6e7278e8951d92a03c1e1f6d63ed339041f69f24c6c0968ba26f244f779cb4fa7a468f3ba3d3e06e";
+  const serializedTx = txFromEtherscan(rawTx);
+
+
+  const tx = eth.signTransaction(
+    "44'/60'/1'/0/0",
+    serializedTx,
+  );
+
+  await waitForAppScreen(sim);
+  await sim.navigateAndCompareSnapshots('.', 'nanos_erc721_safe_transfer', [10, 0]);
+
+  await expect(tx).resolves.toEqual({
+    "r": "b936684d5d0e99e09701021fb73ae9403f2ec79414d822d42c5bd1c0a2118f1a",
+    "s": "23e517c6cac998f392d179be2fe7c3225f0e0a165b1af85548da5d6acaa73c4f",
+    "v": "25",
+  });
+}));
+
+// NOT DONE
+test.skip('[Nano S] safe transfer with data erc721', zemu("nanos", async (sim, eth) => {
+
+  // need to find or create a safe transfer with data on etherscan?
+  const rawTx = "";
+  const serializedTx = txFromEtherscan(rawTx);
+
+
+  const tx = eth.signTransaction(
+    "44'/60'/1'/0/0",
+    serializedTx,
+  );
+
+  await waitForAppScreen(sim);
+  await sim.navigateAndCompareSnapshots('.', 'nanos_erc721_safe_transfer_with_data', [12, 0]);
+
+  await expect(tx).resolves.toEqual({
+  });
+}));
diff --git a/tests/src/send.test.js b/tests/src/send.test.js
index cb82ef439..be27c7805 100644
--- a/tests/src/send.test.js
+++ b/tests/src/send.test.js
@@ -47,7 +47,7 @@ test('[Nano S] Transfer Ether on network 5234 on Ethereum app', zemu("nanos", as
   });
 }));
 
-test('[Nano X] Transfer Ether on Ethereum app', zemu("nanox", async (sim, eth) => {
+test.skip('[Nano X] Transfer Ether on Ethereum app', zemu("nanox", async (sim, eth) => {
 
   const tx = eth.signTransaction(
     "44'/60'/1'/0/0",
@@ -64,7 +64,7 @@ test('[Nano X] Transfer Ether on Ethereum app', zemu("nanox", async (sim, eth) =
   });
 }));
 
-test('[Nano X] Transfer Ether on network 5234 on Ethereum app', zemu("nanox", async (sim, eth) => {
+test.skip('[Nano X] Transfer Ether on network 5234 on Ethereum app', zemu("nanox", async (sim, eth) => {
 
   const tx = eth.signTransaction(
     "44'/60'/1'/0/0",
diff --git a/tests/src/send_bsc.test.js b/tests/src/send_bsc.test.js
index 4e1bfa9e1..02d511281 100644
--- a/tests/src/send_bsc.test.js
+++ b/tests/src/send_bsc.test.js
@@ -19,7 +19,7 @@ test('[Nano S] Transfer bsc', zemu("nanos", async (sim, eth) => {
   });
 }));
 
-test('[Nano X] Transfer bsc', zemu("nanox", async (sim, eth) => {
+test.skip('[Nano X] Transfer bsc', zemu("nanox", async (sim, eth) => {
 
   const tx = eth.signTransaction(
     "44'/60'/1'/0/0",
@@ -34,4 +34,4 @@ test('[Nano X] Transfer bsc', zemu("nanox", async (sim, eth) => {
     "s": "6b35492b7108d9d9e1cc7aede536ed6b3173197b56dd873cbc3b43e041d6f407",
     "v": "93",
   });
-}));
\ No newline at end of file
+}));
diff --git a/tests/src/send_etc.test.js b/tests/src/send_etc.test.js
index 5a2d76c2f..c1cbaa53f 100644
--- a/tests/src/send_etc.test.js
+++ b/tests/src/send_etc.test.js
@@ -60,7 +60,7 @@ test("[Nano S] Transfer on network 5234 on Ethereum clone", async () => {
   }
 });
 
-test("[Nano X] Transfer on Ethereum clone app", async () => {
+test.skip("[Nano X] Transfer on Ethereum clone app", async () => {
   jest.setTimeout(TIMEOUT);
   const sim = new Zemu(NANOX_CLONE_ELF_PATH, NANOX_ETH_LIB);
 
@@ -89,7 +89,7 @@ test("[Nano X] Transfer on Ethereum clone app", async () => {
   }
 });
 
-test("[Nano X] Transfer on network 5234 on Ethereum clone", async () => {
+test.skip("[Nano X] Transfer on network 5234 on Ethereum clone", async () => {
   jest.setTimeout(TIMEOUT);
   const sim = new Zemu(NANOX_CLONE_ELF_PATH, NANOX_ETH_LIB);
 
@@ -111,4 +111,4 @@ test("[Nano X] Transfer on network 5234 on Ethereum clone", async () => {
   } finally {
     await sim.close();
   }
-});
\ No newline at end of file
+});
diff --git a/tests/src/starkware.test.js b/tests/src/starkware.test.js
index 84bb16eca..b4dd707fe 100644
--- a/tests/src/starkware.test.js
+++ b/tests/src/starkware.test.js
@@ -34,7 +34,7 @@ test('[Nano S] Transfer Ether on Ethereum app', zemu("nanos", async (sim, eth) =
   });
 }));
 
-test('[Nano X] Transfer Ether on Ethereum app', zemu("nanox", async (sim, eth) => {
+test.skip('[Nano X] Transfer Ether on Ethereum app', zemu("nanox", async (sim, eth) => {
 
   // Provide USDT token info to the app
   const usdt_info = byContractAddressAndChainId("0xdac17f958d2ee523a2206206994597c13d831ec7", 1);
diff --git a/tests/src/test.fixture.js b/tests/src/test.fixture.js
index fcad8f7dd..70b831337 100644
--- a/tests/src/test.fixture.js
+++ b/tests/src/test.fixture.js
@@ -1,5 +1,6 @@
 import Zemu from '@zondax/zemu';
 import Eth from '@ledgerhq/hw-app-eth';
+import {RLP} from "ethers/lib/utils";
 
 const transactionUploadDelay = 60000;
 
@@ -36,6 +37,35 @@ const NANOX_CLONE_ELF_PATH = Resolve("elfs/ethereum_classic_nanox.elf");
 
 const TIMEOUT = 1000000;
 
+// Generates a serializedTransaction from a rawHexTransaction copy pasted from etherscan.
+function txFromEtherscan(rawTx) {
+    // Remove 0x prefix
+    rawTx = rawTx.slice(2);
+
+    let txType = rawTx.slice(0, 2);
+    if (txType == "02" || txType == "01") {
+        // Remove "02" prefix
+        rawTx = rawTx.slice(2);
+    } else {
+        txType = "";
+    }
+
+    let decoded = RLP.decode("0x" + rawTx);
+    if (txType != "") {
+        decoded = decoded.slice(0, decoded.length - 3); // remove v, r, s
+    } else {
+        decoded[decoded.length - 1] = "0x"; // empty
+        decoded[decoded.length - 2] = "0x"; // empty
+        decoded[decoded.length - 3] = "0x01"; // chainID 1
+    }
+
+    // Encode back the data, drop the '0x' prefix
+    let encoded = RLP.encode(decoded).slice(2);
+
+    // Don't forget to prepend the txtype
+    return txType + encoded;
+}
+
 function zemu(device, func) {
     return async () => {
         jest.setTimeout(TIMEOUT);
@@ -71,5 +101,6 @@ module.exports = {
     NANOX_CLONE_ELF_PATH,
     sim_options_nanos,
     sim_options_nanox,
-    TIMEOUT
+    TIMEOUT,
+    txFromEtherscan,
 }
\ No newline at end of file
diff --git a/tests/yarn.lock b/tests/yarn.lock
index f40fad18e..95fdaafc3 100644
--- a/tests/yarn.lock
+++ b/tests/yarn.lock
@@ -966,6 +966,21 @@
     "@ethersproject/properties" "^5.4.0"
     "@ethersproject/strings" "^5.4.0"
 
+"@ethersproject/abi@5.5.0", "@ethersproject/abi@^5.5.0":
+  version "5.5.0"
+  resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.5.0.tgz#fb52820e22e50b854ff15ce1647cc508d6660613"
+  integrity sha512-loW7I4AohP5KycATvc0MgujU6JyCHPqHdeoo9z3Nr9xEiNioxa65ccdm1+fsoJhkuhdRtfcL8cfyGamz2AxZ5w==
+  dependencies:
+    "@ethersproject/address" "^5.5.0"
+    "@ethersproject/bignumber" "^5.5.0"
+    "@ethersproject/bytes" "^5.5.0"
+    "@ethersproject/constants" "^5.5.0"
+    "@ethersproject/hash" "^5.5.0"
+    "@ethersproject/keccak256" "^5.5.0"
+    "@ethersproject/logger" "^5.5.0"
+    "@ethersproject/properties" "^5.5.0"
+    "@ethersproject/strings" "^5.5.0"
+
 "@ethersproject/abstract-provider@5.4.1", "@ethersproject/abstract-provider@^5.4.0":
   version "5.4.1"
   resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.4.1.tgz#e404309a29f771bd4d28dbafadcaa184668c2a6e"
@@ -979,6 +994,19 @@
     "@ethersproject/transactions" "^5.4.0"
     "@ethersproject/web" "^5.4.0"
 
+"@ethersproject/abstract-provider@5.5.1", "@ethersproject/abstract-provider@^5.5.0":
+  version "5.5.1"
+  resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.5.1.tgz#2f1f6e8a3ab7d378d8ad0b5718460f85649710c5"
+  integrity sha512-m+MA/ful6eKbxpr99xUYeRvLkfnlqzrF8SZ46d/xFB1A7ZVknYc/sXJG0RcufF52Qn2jeFj1hhcoQ7IXjNKUqg==
+  dependencies:
+    "@ethersproject/bignumber" "^5.5.0"
+    "@ethersproject/bytes" "^5.5.0"
+    "@ethersproject/logger" "^5.5.0"
+    "@ethersproject/networks" "^5.5.0"
+    "@ethersproject/properties" "^5.5.0"
+    "@ethersproject/transactions" "^5.5.0"
+    "@ethersproject/web" "^5.5.0"
+
 "@ethersproject/abstract-signer@5.4.1", "@ethersproject/abstract-signer@^5.4.0":
   version "5.4.1"
   resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.4.1.tgz#e4e9abcf4dd4f1ba0db7dff9746a5f78f355ea81"
@@ -990,6 +1018,17 @@
     "@ethersproject/logger" "^5.4.0"
     "@ethersproject/properties" "^5.4.0"
 
+"@ethersproject/abstract-signer@5.5.0", "@ethersproject/abstract-signer@^5.5.0":
+  version "5.5.0"
+  resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.5.0.tgz#590ff6693370c60ae376bf1c7ada59eb2a8dd08d"
+  integrity sha512-lj//7r250MXVLKI7sVarXAbZXbv9P50lgmJQGr2/is82EwEb8r7HrxsmMqAjTsztMYy7ohrIhGMIml+Gx4D3mA==
+  dependencies:
+    "@ethersproject/abstract-provider" "^5.5.0"
+    "@ethersproject/bignumber" "^5.5.0"
+    "@ethersproject/bytes" "^5.5.0"
+    "@ethersproject/logger" "^5.5.0"
+    "@ethersproject/properties" "^5.5.0"
+
 "@ethersproject/address@5.4.0", "@ethersproject/address@^5.4.0":
   version "5.4.0"
   resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.4.0.tgz#ba2d00a0f8c4c0854933b963b9a3a9f6eb4a37a3"
@@ -1001,6 +1040,17 @@
     "@ethersproject/logger" "^5.4.0"
     "@ethersproject/rlp" "^5.4.0"
 
+"@ethersproject/address@5.5.0", "@ethersproject/address@^5.5.0":
+  version "5.5.0"
+  resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.5.0.tgz#bcc6f576a553f21f3dd7ba17248f81b473c9c78f"
+  integrity sha512-l4Nj0eWlTUh6ro5IbPTgbpT4wRbdH5l8CQf7icF7sb/SI3Nhd9Y9HzhonTSTi6CefI0necIw7LJqQPopPLZyWw==
+  dependencies:
+    "@ethersproject/bignumber" "^5.5.0"
+    "@ethersproject/bytes" "^5.5.0"
+    "@ethersproject/keccak256" "^5.5.0"
+    "@ethersproject/logger" "^5.5.0"
+    "@ethersproject/rlp" "^5.5.0"
+
 "@ethersproject/base64@5.4.0", "@ethersproject/base64@^5.4.0":
   version "5.4.0"
   resolved "https://registry.yarnpkg.com/@ethersproject/base64/-/base64-5.4.0.tgz#7252bf65295954c9048c7ca5f43e5c86441b2a9a"
@@ -1008,6 +1058,13 @@
   dependencies:
     "@ethersproject/bytes" "^5.4.0"
 
+"@ethersproject/base64@5.5.0", "@ethersproject/base64@^5.5.0":
+  version "5.5.0"
+  resolved "https://registry.yarnpkg.com/@ethersproject/base64/-/base64-5.5.0.tgz#881e8544e47ed976930836986e5eb8fab259c090"
+  integrity sha512-tdayUKhU1ljrlHzEWbStXazDpsx4eg1dBXUSI6+mHlYklOXoXF6lZvw8tnD6oVaWfnMxAgRSKROg3cVKtCcppA==
+  dependencies:
+    "@ethersproject/bytes" "^5.5.0"
+
 "@ethersproject/basex@5.4.0", "@ethersproject/basex@^5.4.0":
   version "5.4.0"
   resolved "https://registry.yarnpkg.com/@ethersproject/basex/-/basex-5.4.0.tgz#0a2da0f4e76c504a94f2b21d3161ed9438c7f8a6"
@@ -1016,6 +1073,14 @@
     "@ethersproject/bytes" "^5.4.0"
     "@ethersproject/properties" "^5.4.0"
 
+"@ethersproject/basex@5.5.0", "@ethersproject/basex@^5.5.0":
+  version "5.5.0"
+  resolved "https://registry.yarnpkg.com/@ethersproject/basex/-/basex-5.5.0.tgz#e40a53ae6d6b09ab4d977bd037010d4bed21b4d3"
+  integrity sha512-ZIodwhHpVJ0Y3hUCfUucmxKsWQA5TMnavp5j/UOuDdzZWzJlRmuOjcTMIGgHCYuZmHt36BfiSyQPSRskPxbfaQ==
+  dependencies:
+    "@ethersproject/bytes" "^5.5.0"
+    "@ethersproject/properties" "^5.5.0"
+
 "@ethersproject/bignumber@5.4.1", "@ethersproject/bignumber@^5.4.0":
   version "5.4.1"
   resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.4.1.tgz#64399d3b9ae80aa83d483e550ba57ea062c1042d"
@@ -1025,6 +1090,15 @@
     "@ethersproject/logger" "^5.4.0"
     bn.js "^4.11.9"
 
+"@ethersproject/bignumber@5.5.0", "@ethersproject/bignumber@^5.5.0":
+  version "5.5.0"
+  resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.5.0.tgz#875b143f04a216f4f8b96245bde942d42d279527"
+  integrity sha512-6Xytlwvy6Rn3U3gKEc1vP7nR92frHkv6wtVr95LFR3jREXiCPzdWxKQ1cx4JGQBXxcguAwjA8murlYN2TSiEbg==
+  dependencies:
+    "@ethersproject/bytes" "^5.5.0"
+    "@ethersproject/logger" "^5.5.0"
+    bn.js "^4.11.9"
+
 "@ethersproject/bytes@5.4.0", "@ethersproject/bytes@^5.4.0":
   version "5.4.0"
   resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.4.0.tgz#56fa32ce3bf67153756dbaefda921d1d4774404e"
@@ -1032,6 +1106,13 @@
   dependencies:
     "@ethersproject/logger" "^5.4.0"
 
+"@ethersproject/bytes@5.5.0", "@ethersproject/bytes@^5.5.0":
+  version "5.5.0"
+  resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.5.0.tgz#cb11c526de657e7b45d2e0f0246fb3b9d29a601c"
+  integrity sha512-ABvc7BHWhZU9PNM/tANm/Qx4ostPGadAuQzWTr3doklZOhDlmcBqclrQe/ZXUIj3K8wC28oYeuRa+A37tX9kog==
+  dependencies:
+    "@ethersproject/logger" "^5.5.0"
+
 "@ethersproject/constants@5.4.0", "@ethersproject/constants@^5.4.0":
   version "5.4.0"
   resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.4.0.tgz#ee0bdcb30bf1b532d2353c977bf2ef1ee117958a"
@@ -1039,6 +1120,13 @@
   dependencies:
     "@ethersproject/bignumber" "^5.4.0"
 
+"@ethersproject/constants@5.5.0", "@ethersproject/constants@^5.5.0":
+  version "5.5.0"
+  resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.5.0.tgz#d2a2cd7d94bd1d58377d1d66c4f53c9be4d0a45e"
+  integrity sha512-2MsRRVChkvMWR+GyMGY4N1sAX9Mt3J9KykCsgUFd/1mwS0UH1qw+Bv9k1UJb3X3YJYFco9H20pjSlOIfCG5HYQ==
+  dependencies:
+    "@ethersproject/bignumber" "^5.5.0"
+
 "@ethersproject/contracts@5.4.1":
   version "5.4.1"
   resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.4.1.tgz#3eb4f35b7fe60a962a75804ada2746494df3e470"
@@ -1055,6 +1143,22 @@
     "@ethersproject/properties" "^5.4.0"
     "@ethersproject/transactions" "^5.4.0"
 
+"@ethersproject/contracts@5.5.0":
+  version "5.5.0"
+  resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.5.0.tgz#b735260d4bd61283a670a82d5275e2a38892c197"
+  integrity sha512-2viY7NzyvJkh+Ug17v7g3/IJC8HqZBDcOjYARZLdzRxrfGlRgmYgl6xPRKVbEzy1dWKw/iv7chDcS83pg6cLxg==
+  dependencies:
+    "@ethersproject/abi" "^5.5.0"
+    "@ethersproject/abstract-provider" "^5.5.0"
+    "@ethersproject/abstract-signer" "^5.5.0"
+    "@ethersproject/address" "^5.5.0"
+    "@ethersproject/bignumber" "^5.5.0"
+    "@ethersproject/bytes" "^5.5.0"
+    "@ethersproject/constants" "^5.5.0"
+    "@ethersproject/logger" "^5.5.0"
+    "@ethersproject/properties" "^5.5.0"
+    "@ethersproject/transactions" "^5.5.0"
+
 "@ethersproject/hash@5.4.0", "@ethersproject/hash@^5.4.0":
   version "5.4.0"
   resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.4.0.tgz#d18a8e927e828e22860a011f39e429d388344ae0"
@@ -1069,6 +1173,20 @@
     "@ethersproject/properties" "^5.4.0"
     "@ethersproject/strings" "^5.4.0"
 
+"@ethersproject/hash@5.5.0", "@ethersproject/hash@^5.5.0":
+  version "5.5.0"
+  resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.5.0.tgz#7cee76d08f88d1873574c849e0207dcb32380cc9"
+  integrity sha512-dnGVpK1WtBjmnp3mUT0PlU2MpapnwWI0PibldQEq1408tQBAbZpPidkWoVVuNMOl/lISO3+4hXZWCL3YV7qzfg==
+  dependencies:
+    "@ethersproject/abstract-signer" "^5.5.0"
+    "@ethersproject/address" "^5.5.0"
+    "@ethersproject/bignumber" "^5.5.0"
+    "@ethersproject/bytes" "^5.5.0"
+    "@ethersproject/keccak256" "^5.5.0"
+    "@ethersproject/logger" "^5.5.0"
+    "@ethersproject/properties" "^5.5.0"
+    "@ethersproject/strings" "^5.5.0"
+
 "@ethersproject/hdnode@5.4.0", "@ethersproject/hdnode@^5.4.0":
   version "5.4.0"
   resolved "https://registry.yarnpkg.com/@ethersproject/hdnode/-/hdnode-5.4.0.tgz#4bc9999b9a12eb5ce80c5faa83114a57e4107cac"
@@ -1087,6 +1205,24 @@
     "@ethersproject/transactions" "^5.4.0"
     "@ethersproject/wordlists" "^5.4.0"
 
+"@ethersproject/hdnode@5.5.0", "@ethersproject/hdnode@^5.5.0":
+  version "5.5.0"
+  resolved "https://registry.yarnpkg.com/@ethersproject/hdnode/-/hdnode-5.5.0.tgz#4a04e28f41c546f7c978528ea1575206a200ddf6"
+  integrity sha512-mcSOo9zeUg1L0CoJH7zmxwUG5ggQHU1UrRf8jyTYy6HxdZV+r0PBoL1bxr+JHIPXRzS6u/UW4mEn43y0tmyF8Q==
+  dependencies:
+    "@ethersproject/abstract-signer" "^5.5.0"
+    "@ethersproject/basex" "^5.5.0"
+    "@ethersproject/bignumber" "^5.5.0"
+    "@ethersproject/bytes" "^5.5.0"
+    "@ethersproject/logger" "^5.5.0"
+    "@ethersproject/pbkdf2" "^5.5.0"
+    "@ethersproject/properties" "^5.5.0"
+    "@ethersproject/sha2" "^5.5.0"
+    "@ethersproject/signing-key" "^5.5.0"
+    "@ethersproject/strings" "^5.5.0"
+    "@ethersproject/transactions" "^5.5.0"
+    "@ethersproject/wordlists" "^5.5.0"
+
 "@ethersproject/json-wallets@5.4.0", "@ethersproject/json-wallets@^5.4.0":
   version "5.4.0"
   resolved "https://registry.yarnpkg.com/@ethersproject/json-wallets/-/json-wallets-5.4.0.tgz#2583341cfe313fc9856642e8ace3080154145e95"
@@ -1106,6 +1242,25 @@
     aes-js "3.0.0"
     scrypt-js "3.0.1"
 
+"@ethersproject/json-wallets@5.5.0", "@ethersproject/json-wallets@^5.5.0":
+  version "5.5.0"
+  resolved "https://registry.yarnpkg.com/@ethersproject/json-wallets/-/json-wallets-5.5.0.tgz#dd522d4297e15bccc8e1427d247ec8376b60e325"
+  integrity sha512-9lA21XQnCdcS72xlBn1jfQdj2A1VUxZzOzi9UkNdnokNKke/9Ya2xA9aIK1SC3PQyBDLt4C+dfps7ULpkvKikQ==
+  dependencies:
+    "@ethersproject/abstract-signer" "^5.5.0"
+    "@ethersproject/address" "^5.5.0"
+    "@ethersproject/bytes" "^5.5.0"
+    "@ethersproject/hdnode" "^5.5.0"
+    "@ethersproject/keccak256" "^5.5.0"
+    "@ethersproject/logger" "^5.5.0"
+    "@ethersproject/pbkdf2" "^5.5.0"
+    "@ethersproject/properties" "^5.5.0"
+    "@ethersproject/random" "^5.5.0"
+    "@ethersproject/strings" "^5.5.0"
+    "@ethersproject/transactions" "^5.5.0"
+    aes-js "3.0.0"
+    scrypt-js "3.0.1"
+
 "@ethersproject/keccak256@5.4.0", "@ethersproject/keccak256@^5.4.0":
   version "5.4.0"
   resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.4.0.tgz#7143b8eea4976080241d2bd92e3b1f1bf7025318"
@@ -1114,11 +1269,24 @@
     "@ethersproject/bytes" "^5.4.0"
     js-sha3 "0.5.7"
 
+"@ethersproject/keccak256@5.5.0", "@ethersproject/keccak256@^5.5.0":
+  version "5.5.0"
+  resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.5.0.tgz#e4b1f9d7701da87c564ffe336f86dcee82983492"
+  integrity sha512-5VoFCTjo2rYbBe1l2f4mccaRFN/4VQEYFwwn04aJV2h7qf4ZvI2wFxUE1XOX+snbwCLRzIeikOqtAoPwMza9kg==
+  dependencies:
+    "@ethersproject/bytes" "^5.5.0"
+    js-sha3 "0.8.0"
+
 "@ethersproject/logger@5.4.0", "@ethersproject/logger@^5.4.0":
   version "5.4.0"
   resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.4.0.tgz#f39adadf62ad610c420bcd156fd41270e91b3ca9"
   integrity sha512-xYdWGGQ9P2cxBayt64d8LC8aPFJk6yWCawQi/4eJ4+oJdMMjEBMrIcIMZ9AxhwpPVmnBPrsB10PcXGmGAqgUEQ==
 
+"@ethersproject/logger@5.5.0", "@ethersproject/logger@^5.5.0":
+  version "5.5.0"
+  resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.5.0.tgz#0c2caebeff98e10aefa5aef27d7441c7fd18cf5d"
+  integrity sha512-rIY/6WPm7T8n3qS2vuHTUBPdXHl+rGxWxW5okDfo9J4Z0+gRRZT0msvUdIJkE4/HS29GUMziwGaaKO2bWONBrg==
+
 "@ethersproject/networks@5.4.2", "@ethersproject/networks@^5.4.0":
   version "5.4.2"
   resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.4.2.tgz#2247d977626e97e2c3b8ee73cd2457babde0ce35"
@@ -1126,6 +1294,13 @@
   dependencies:
     "@ethersproject/logger" "^5.4.0"
 
+"@ethersproject/networks@5.5.0", "@ethersproject/networks@^5.5.0":
+  version "5.5.0"
+  resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.5.0.tgz#babec47cab892c51f8dd652ce7f2e3e14283981a"
+  integrity sha512-KWfP3xOnJeF89Uf/FCJdV1a2aDJe5XTN2N52p4fcQ34QhDqQFkgQKZ39VGtiqUgHcLI8DfT0l9azC3KFTunqtA==
+  dependencies:
+    "@ethersproject/logger" "^5.5.0"
+
 "@ethersproject/pbkdf2@5.4.0", "@ethersproject/pbkdf2@^5.4.0":
   version "5.4.0"
   resolved "https://registry.yarnpkg.com/@ethersproject/pbkdf2/-/pbkdf2-5.4.0.tgz#ed88782a67fda1594c22d60d0ca911a9d669641c"
@@ -1134,6 +1309,14 @@
     "@ethersproject/bytes" "^5.4.0"
     "@ethersproject/sha2" "^5.4.0"
 
+"@ethersproject/pbkdf2@5.5.0", "@ethersproject/pbkdf2@^5.5.0":
+  version "5.5.0"
+  resolved "https://registry.yarnpkg.com/@ethersproject/pbkdf2/-/pbkdf2-5.5.0.tgz#e25032cdf02f31505d47afbf9c3e000d95c4a050"
+  integrity sha512-SaDvQFvXPnz1QGpzr6/HToLifftSXGoXrbpZ6BvoZhmx4bNLHrxDe8MZisuecyOziP1aVEwzC2Hasj+86TgWVg==
+  dependencies:
+    "@ethersproject/bytes" "^5.5.0"
+    "@ethersproject/sha2" "^5.5.0"
+
 "@ethersproject/properties@5.4.0", "@ethersproject/properties@^5.4.0":
   version "5.4.0"
   resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.4.0.tgz#38ba20539b44dcc5d5f80c45ad902017dcdbefe7"
@@ -1141,6 +1324,13 @@
   dependencies:
     "@ethersproject/logger" "^5.4.0"
 
+"@ethersproject/properties@5.5.0", "@ethersproject/properties@^5.5.0":
+  version "5.5.0"
+  resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.5.0.tgz#61f00f2bb83376d2071baab02245f92070c59995"
+  integrity sha512-l3zRQg3JkD8EL3CPjNK5g7kMx4qSwiR60/uk5IVjd3oq1MZR5qUg40CNOoEJoX5wc3DyY5bt9EbMk86C7x0DNA==
+  dependencies:
+    "@ethersproject/logger" "^5.5.0"
+
 "@ethersproject/providers@5.4.4":
   version "5.4.4"
   resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.4.4.tgz#6729120317942fc0ab0ecdb35e944ec6bbedb795"
@@ -1166,6 +1356,31 @@
     bech32 "1.1.4"
     ws "7.4.6"
 
+"@ethersproject/providers@5.5.0":
+  version "5.5.0"
+  resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.5.0.tgz#bc2876a8fe5e0053ed9828b1f3767ae46e43758b"
+  integrity sha512-xqMbDnS/FPy+J/9mBLKddzyLLAQFjrVff5g00efqxPzcAwXiR+SiCGVy6eJ5iAIirBOATjx7QLhDNPGV+AEQsw==
+  dependencies:
+    "@ethersproject/abstract-provider" "^5.5.0"
+    "@ethersproject/abstract-signer" "^5.5.0"
+    "@ethersproject/address" "^5.5.0"
+    "@ethersproject/basex" "^5.5.0"
+    "@ethersproject/bignumber" "^5.5.0"
+    "@ethersproject/bytes" "^5.5.0"
+    "@ethersproject/constants" "^5.5.0"
+    "@ethersproject/hash" "^5.5.0"
+    "@ethersproject/logger" "^5.5.0"
+    "@ethersproject/networks" "^5.5.0"
+    "@ethersproject/properties" "^5.5.0"
+    "@ethersproject/random" "^5.5.0"
+    "@ethersproject/rlp" "^5.5.0"
+    "@ethersproject/sha2" "^5.5.0"
+    "@ethersproject/strings" "^5.5.0"
+    "@ethersproject/transactions" "^5.5.0"
+    "@ethersproject/web" "^5.5.0"
+    bech32 "1.1.4"
+    ws "7.4.6"
+
 "@ethersproject/random@5.4.0", "@ethersproject/random@^5.4.0":
   version "5.4.0"
   resolved "https://registry.yarnpkg.com/@ethersproject/random/-/random-5.4.0.tgz#9cdde60e160d024be39cc16f8de3b9ce39191e16"
@@ -1174,6 +1389,14 @@
     "@ethersproject/bytes" "^5.4.0"
     "@ethersproject/logger" "^5.4.0"
 
+"@ethersproject/random@5.5.0", "@ethersproject/random@^5.5.0":
+  version "5.5.0"
+  resolved "https://registry.yarnpkg.com/@ethersproject/random/-/random-5.5.0.tgz#305ed9e033ca537735365ac12eed88580b0f81f9"
+  integrity sha512-egGYZwZ/YIFKMHcoBUo8t3a8Hb/TKYX8BCBoLjudVCZh892welR3jOxgOmb48xznc9bTcMm7Tpwc1gHC1PFNFQ==
+  dependencies:
+    "@ethersproject/bytes" "^5.5.0"
+    "@ethersproject/logger" "^5.5.0"
+
 "@ethersproject/rlp@5.4.0", "@ethersproject/rlp@^5.4.0":
   version "5.4.0"
   resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.4.0.tgz#de61afda5ff979454e76d3b3310a6c32ad060931"
@@ -1182,6 +1405,14 @@
     "@ethersproject/bytes" "^5.4.0"
     "@ethersproject/logger" "^5.4.0"
 
+"@ethersproject/rlp@5.5.0", "@ethersproject/rlp@^5.5.0":
+  version "5.5.0"
+  resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.5.0.tgz#530f4f608f9ca9d4f89c24ab95db58ab56ab99a0"
+  integrity sha512-hLv8XaQ8PTI9g2RHoQGf/WSxBfTB/NudRacbzdxmst5VHAqd1sMibWG7SENzT5Dj3yZ3kJYx+WiRYEcQTAkcYA==
+  dependencies:
+    "@ethersproject/bytes" "^5.5.0"
+    "@ethersproject/logger" "^5.5.0"
+
 "@ethersproject/sha2@5.4.0", "@ethersproject/sha2@^5.4.0":
   version "5.4.0"
   resolved "https://registry.yarnpkg.com/@ethersproject/sha2/-/sha2-5.4.0.tgz#c9a8db1037014cbc4e9482bd662f86c090440371"
@@ -1191,6 +1422,15 @@
     "@ethersproject/logger" "^5.4.0"
     hash.js "1.1.7"
 
+"@ethersproject/sha2@5.5.0", "@ethersproject/sha2@^5.5.0":
+  version "5.5.0"
+  resolved "https://registry.yarnpkg.com/@ethersproject/sha2/-/sha2-5.5.0.tgz#a40a054c61f98fd9eee99af2c3cc6ff57ec24db7"
+  integrity sha512-B5UBoglbCiHamRVPLA110J+2uqsifpZaTmid2/7W5rbtYVz6gus6/hSDieIU/6gaKIDcOj12WnOdiymEUHIAOA==
+  dependencies:
+    "@ethersproject/bytes" "^5.5.0"
+    "@ethersproject/logger" "^5.5.0"
+    hash.js "1.1.7"
+
 "@ethersproject/signing-key@5.4.0", "@ethersproject/signing-key@^5.4.0":
   version "5.4.0"
   resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.4.0.tgz#2f05120984e81cf89a3d5f6dec5c68ee0894fbec"
@@ -1203,6 +1443,18 @@
     elliptic "6.5.4"
     hash.js "1.1.7"
 
+"@ethersproject/signing-key@5.5.0", "@ethersproject/signing-key@^5.5.0":
+  version "5.5.0"
+  resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.5.0.tgz#2aa37169ce7e01e3e80f2c14325f624c29cedbe0"
+  integrity sha512-5VmseH7qjtNmDdZBswavhotYbWB0bOwKIlOTSlX14rKn5c11QmJwGt4GHeo7NrL/Ycl7uo9AHvEqs5xZgFBTng==
+  dependencies:
+    "@ethersproject/bytes" "^5.5.0"
+    "@ethersproject/logger" "^5.5.0"
+    "@ethersproject/properties" "^5.5.0"
+    bn.js "^4.11.9"
+    elliptic "6.5.4"
+    hash.js "1.1.7"
+
 "@ethersproject/solidity@5.4.0":
   version "5.4.0"
   resolved "https://registry.yarnpkg.com/@ethersproject/solidity/-/solidity-5.4.0.tgz#1305e058ea02dc4891df18b33232b11a14ece9ec"
@@ -1214,6 +1466,18 @@
     "@ethersproject/sha2" "^5.4.0"
     "@ethersproject/strings" "^5.4.0"
 
+"@ethersproject/solidity@5.5.0":
+  version "5.5.0"
+  resolved "https://registry.yarnpkg.com/@ethersproject/solidity/-/solidity-5.5.0.tgz#2662eb3e5da471b85a20531e420054278362f93f"
+  integrity sha512-9NgZs9LhGMj6aCtHXhtmFQ4AN4sth5HuFXVvAQtzmm0jpSCNOTGtrHZJAeYTh7MBjRR8brylWZxBZR9zDStXbw==
+  dependencies:
+    "@ethersproject/bignumber" "^5.5.0"
+    "@ethersproject/bytes" "^5.5.0"
+    "@ethersproject/keccak256" "^5.5.0"
+    "@ethersproject/logger" "^5.5.0"
+    "@ethersproject/sha2" "^5.5.0"
+    "@ethersproject/strings" "^5.5.0"
+
 "@ethersproject/strings@5.4.0", "@ethersproject/strings@^5.4.0":
   version "5.4.0"
   resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.4.0.tgz#fb12270132dd84b02906a8d895ae7e7fa3d07d9a"
@@ -1223,6 +1487,15 @@
     "@ethersproject/constants" "^5.4.0"
     "@ethersproject/logger" "^5.4.0"
 
+"@ethersproject/strings@5.5.0", "@ethersproject/strings@^5.5.0":
+  version "5.5.0"
+  resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.5.0.tgz#e6784d00ec6c57710755699003bc747e98c5d549"
+  integrity sha512-9fy3TtF5LrX/wTrBaT8FGE6TDJyVjOvXynXJz5MT5azq+E6D92zuKNx7i29sWW2FjVOaWjAsiZ1ZWznuduTIIQ==
+  dependencies:
+    "@ethersproject/bytes" "^5.5.0"
+    "@ethersproject/constants" "^5.5.0"
+    "@ethersproject/logger" "^5.5.0"
+
 "@ethersproject/transactions@5.4.0", "@ethersproject/transactions@^5.4.0":
   version "5.4.0"
   resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.4.0.tgz#a159d035179334bd92f340ce0f77e83e9e1522e0"
@@ -1238,6 +1511,21 @@
     "@ethersproject/rlp" "^5.4.0"
     "@ethersproject/signing-key" "^5.4.0"
 
+"@ethersproject/transactions@5.5.0", "@ethersproject/transactions@^5.5.0":
+  version "5.5.0"
+  resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.5.0.tgz#7e9bf72e97bcdf69db34fe0d59e2f4203c7a2908"
+  integrity sha512-9RZYSKX26KfzEd/1eqvv8pLauCKzDTub0Ko4LfIgaERvRuwyaNV78mJs7cpIgZaDl6RJui4o49lHwwCM0526zA==
+  dependencies:
+    "@ethersproject/address" "^5.5.0"
+    "@ethersproject/bignumber" "^5.5.0"
+    "@ethersproject/bytes" "^5.5.0"
+    "@ethersproject/constants" "^5.5.0"
+    "@ethersproject/keccak256" "^5.5.0"
+    "@ethersproject/logger" "^5.5.0"
+    "@ethersproject/properties" "^5.5.0"
+    "@ethersproject/rlp" "^5.5.0"
+    "@ethersproject/signing-key" "^5.5.0"
+
 "@ethersproject/units@5.4.0":
   version "5.4.0"
   resolved "https://registry.yarnpkg.com/@ethersproject/units/-/units-5.4.0.tgz#d57477a4498b14b88b10396062c8cbbaf20c79fe"
@@ -1247,6 +1535,15 @@
     "@ethersproject/constants" "^5.4.0"
     "@ethersproject/logger" "^5.4.0"
 
+"@ethersproject/units@5.5.0":
+  version "5.5.0"
+  resolved "https://registry.yarnpkg.com/@ethersproject/units/-/units-5.5.0.tgz#104d02db5b5dc42cc672cc4587bafb87a95ee45e"
+  integrity sha512-7+DpjiZk4v6wrikj+TCyWWa9dXLNU73tSTa7n0TSJDxkYbV3Yf1eRh9ToMLlZtuctNYu9RDNNy2USq3AdqSbag==
+  dependencies:
+    "@ethersproject/bignumber" "^5.5.0"
+    "@ethersproject/constants" "^5.5.0"
+    "@ethersproject/logger" "^5.5.0"
+
 "@ethersproject/wallet@5.4.0":
   version "5.4.0"
   resolved "https://registry.yarnpkg.com/@ethersproject/wallet/-/wallet-5.4.0.tgz#fa5b59830b42e9be56eadd45a16a2e0933ad9353"
@@ -1268,6 +1565,27 @@
     "@ethersproject/transactions" "^5.4.0"
     "@ethersproject/wordlists" "^5.4.0"
 
+"@ethersproject/wallet@5.5.0":
+  version "5.5.0"
+  resolved "https://registry.yarnpkg.com/@ethersproject/wallet/-/wallet-5.5.0.tgz#322a10527a440ece593980dca6182f17d54eae75"
+  integrity sha512-Mlu13hIctSYaZmUOo7r2PhNSd8eaMPVXe1wxrz4w4FCE4tDYBywDH+bAR1Xz2ADyXGwqYMwstzTrtUVIsKDO0Q==
+  dependencies:
+    "@ethersproject/abstract-provider" "^5.5.0"
+    "@ethersproject/abstract-signer" "^5.5.0"
+    "@ethersproject/address" "^5.5.0"
+    "@ethersproject/bignumber" "^5.5.0"
+    "@ethersproject/bytes" "^5.5.0"
+    "@ethersproject/hash" "^5.5.0"
+    "@ethersproject/hdnode" "^5.5.0"
+    "@ethersproject/json-wallets" "^5.5.0"
+    "@ethersproject/keccak256" "^5.5.0"
+    "@ethersproject/logger" "^5.5.0"
+    "@ethersproject/properties" "^5.5.0"
+    "@ethersproject/random" "^5.5.0"
+    "@ethersproject/signing-key" "^5.5.0"
+    "@ethersproject/transactions" "^5.5.0"
+    "@ethersproject/wordlists" "^5.5.0"
+
 "@ethersproject/web@5.4.0", "@ethersproject/web@^5.4.0":
   version "5.4.0"
   resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.4.0.tgz#49fac173b96992334ed36a175538ba07a7413d1f"
@@ -1279,6 +1597,17 @@
     "@ethersproject/properties" "^5.4.0"
     "@ethersproject/strings" "^5.4.0"
 
+"@ethersproject/web@5.5.0", "@ethersproject/web@^5.5.0":
+  version "5.5.0"
+  resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.5.0.tgz#0e5bb21a2b58fb4960a705bfc6522a6acf461e28"
+  integrity sha512-BEgY0eL5oH4mAo37TNYVrFeHsIXLRxggCRG/ksRIxI2X5uj5IsjGmcNiRN/VirQOlBxcUhCgHhaDLG4m6XAVoA==
+  dependencies:
+    "@ethersproject/base64" "^5.5.0"
+    "@ethersproject/bytes" "^5.5.0"
+    "@ethersproject/logger" "^5.5.0"
+    "@ethersproject/properties" "^5.5.0"
+    "@ethersproject/strings" "^5.5.0"
+
 "@ethersproject/wordlists@5.4.0", "@ethersproject/wordlists@^5.4.0":
   version "5.4.0"
   resolved "https://registry.yarnpkg.com/@ethersproject/wordlists/-/wordlists-5.4.0.tgz#f34205ec3bbc9e2c49cadaee774cf0b07e7573d7"
@@ -1290,6 +1619,17 @@
     "@ethersproject/properties" "^5.4.0"
     "@ethersproject/strings" "^5.4.0"
 
+"@ethersproject/wordlists@5.5.0", "@ethersproject/wordlists@^5.5.0":
+  version "5.5.0"
+  resolved "https://registry.yarnpkg.com/@ethersproject/wordlists/-/wordlists-5.5.0.tgz#aac74963aa43e643638e5172353d931b347d584f"
+  integrity sha512-bL0UTReWDiaQJJYOC9sh/XcRu/9i2jMrzf8VLRmPKx58ckSlOJiohODkECCO50dtLZHcGU6MLXQ4OOrgBwP77Q==
+  dependencies:
+    "@ethersproject/bytes" "^5.5.0"
+    "@ethersproject/hash" "^5.5.0"
+    "@ethersproject/logger" "^5.5.0"
+    "@ethersproject/properties" "^5.5.0"
+    "@ethersproject/strings" "^5.5.0"
+
 "@grpc/grpc-js@^1.3.4":
   version "1.3.7"
   resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.3.7.tgz#58b687aff93b743aafde237fd2ee9a3259d7f2d8"
@@ -3176,6 +3516,42 @@ ethers@^5.4.4:
     "@ethersproject/web" "5.4.0"
     "@ethersproject/wordlists" "5.4.0"
 
+ethers@^5.5.1:
+  version "5.5.1"
+  resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.5.1.tgz#d3259a95a42557844aa543906c537106c0406fbf"
+  integrity sha512-RodEvUFZI+EmFcE6bwkuJqpCYHazdzeR1nMzg+YWQSmQEsNtfl1KHGfp/FWZYl48bI/g7cgBeP2IlPthjiVngw==
+  dependencies:
+    "@ethersproject/abi" "5.5.0"
+    "@ethersproject/abstract-provider" "5.5.1"
+    "@ethersproject/abstract-signer" "5.5.0"
+    "@ethersproject/address" "5.5.0"
+    "@ethersproject/base64" "5.5.0"
+    "@ethersproject/basex" "5.5.0"
+    "@ethersproject/bignumber" "5.5.0"
+    "@ethersproject/bytes" "5.5.0"
+    "@ethersproject/constants" "5.5.0"
+    "@ethersproject/contracts" "5.5.0"
+    "@ethersproject/hash" "5.5.0"
+    "@ethersproject/hdnode" "5.5.0"
+    "@ethersproject/json-wallets" "5.5.0"
+    "@ethersproject/keccak256" "5.5.0"
+    "@ethersproject/logger" "5.5.0"
+    "@ethersproject/networks" "5.5.0"
+    "@ethersproject/pbkdf2" "5.5.0"
+    "@ethersproject/properties" "5.5.0"
+    "@ethersproject/providers" "5.5.0"
+    "@ethersproject/random" "5.5.0"
+    "@ethersproject/rlp" "5.5.0"
+    "@ethersproject/sha2" "5.5.0"
+    "@ethersproject/signing-key" "5.5.0"
+    "@ethersproject/solidity" "5.5.0"
+    "@ethersproject/strings" "5.5.0"
+    "@ethersproject/transactions" "5.5.0"
+    "@ethersproject/units" "5.5.0"
+    "@ethersproject/wallet" "5.5.0"
+    "@ethersproject/web" "5.5.0"
+    "@ethersproject/wordlists" "5.5.0"
+
 events@^3.0.0, events@^3.3.0:
   version "3.3.0"
   resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"
@@ -4806,6 +5182,11 @@ js-sha3@0.5.7:
   resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.5.7.tgz#0d4ffd8002d5333aabaf4a23eed2f6374c9f28e7"
   integrity sha1-DU/9gALVMzqrr0oj7tL2N0yfKOc=
 
+js-sha3@0.8.0:
+  version "0.8.0"
+  resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840"
+  integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==
+
 "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"