Repository: hyperledger/firefly
Pull request #837 (2022-05-27 17:48:56)

Use firefly-signer library and allow numbers expressed as strings
This PR introduces `firefly-signer`'s ABI parsing functions and uses them in FireFly's ABI -> FFI generation code. It also replaces any existing ABI data structures that were originally copied over from Ethconnect, with those of `firefly-signer`.

This PR also makes some enhancements to the ABI -> FFI generation code itself to allow numbers to be expressed in a JSON string or a JSON number. This is reflected in the generated OpenAPI Spec for custom contracts, including a description in the Swagger UI that describes this.

Further improvements have also been made to the OpenAPI Spec / Swagger UI for custom smart contracts, to indicate if a function is payable, etc. The `options` object also appears in the schema to make it more obvious how to pass in additional options.

A new `details` field has been added to `FFIMethod` and `FFIEventDefinition` structs. In the same way that a `details` field has always wrapped blockchain specific information for a `FFIParam`, we now have this ability on a function and event level as well. For an Ethereum smart contract, this is used to indicate whether a function is payable, whether an event is anonymous, etc. The details are simply stored as a `fftypes.JSONObject`. In the rendered Swagger UI, a table is constructed to show each field and its corresponding value in the Function / Event description. 


Patch:
diff --git a/db/migrations/postgres/000093_add_ffi_details.down.sql b/db/migrations/postgres/000093_add_ffi_details.down.sql
new file mode 100644
index 000000000..cc95ed927
--- /dev/null
+++ b/db/migrations/postgres/000093_add_ffi_details.down.sql
@@ -0,0 +1,4 @@
+BEGIN;
+ALTER TABLE ffimethods DROP COLUMN details;
+ALTER TABLE ffievents DROP COLUMN details;
+COMMIT;
\ No newline at end of file
diff --git a/db/migrations/postgres/000093_add_ffi_details.up.sql b/db/migrations/postgres/000093_add_ffi_details.up.sql
new file mode 100644
index 000000000..7cf46ed61
--- /dev/null
+++ b/db/migrations/postgres/000093_add_ffi_details.up.sql
@@ -0,0 +1,4 @@
+BEGIN;
+ALTER TABLE ffimethods ADD COLUMN details TEXT;
+ALTER TABLE ffievents ADD COLUMN details TEXT;
+COMMIT;
\ No newline at end of file
diff --git a/db/migrations/sqlite/000093_add_ffi_details.down.sql b/db/migrations/sqlite/000093_add_ffi_details.down.sql
new file mode 100644
index 000000000..90f917aaf
--- /dev/null
+++ b/db/migrations/sqlite/000093_add_ffi_details.down.sql
@@ -0,0 +1,2 @@
+ALTER TABLE ffimethods DROP COLUMN details;
+ALTER TABLE ffievents DROP COLUMN details;
diff --git a/db/migrations/sqlite/000093_add_ffi_details.up.sql b/db/migrations/sqlite/000093_add_ffi_details.up.sql
new file mode 100644
index 000000000..28435d439
--- /dev/null
+++ b/db/migrations/sqlite/000093_add_ffi_details.up.sql
@@ -0,0 +1,2 @@
+ALTER TABLE ffimethods ADD COLUMN details TEXT;
+ALTER TABLE ffievents ADD COLUMN details TEXT;
diff --git a/docs/reference/firefly_interface_format.md b/docs/reference/firefly_interface_format.md
index 2daf90b87..7f77b506b 100644
--- a/docs/reference/firefly_interface_format.md
+++ b/docs/reference/firefly_interface_format.md
@@ -35,26 +35,28 @@ There are four required fields when broadcasting a contract interface in FireFly
 
 ## Method
 
-Let's look at a what goes inside the `methods` array now. It is also a JSON object that has a `name`, a list of `params` which are the arguments the function will take and a list of `returns` which are the return values of the function. Optionally, it also has a `description` which can be helpful in OpenAPI Spec generation.
+Let's look at a what goes inside the `methods` array now. It is also a JSON object that has a `name`, a list of `params` which are the arguments the function will take and a list of `returns` which are the return values of the function. It also has an optional `description` which can be helpful in OpenAPI Spec generation. Finally, it has an optional `details` object which wraps blockchain specific information about this method. This can be used by the blockchain plugin when invoking this function, and it is also used in documentation generation.
 
 ```json
 {
     "name": "add",
     "description": "Add two numbers together",
     "params": [],
-    "returns": []
+    "returns": [],
+    "details": {}
 }
 ```
 
 ## Event
 
-What goes into the `events` array is very similar. It is also a JSON object that has a `name` and a list of `params`. The difference is that `events` don't have `returns`. Arguments that are passed to the event when it is emitted are in `params`. Optionally, it also has a `description` which can be helpful in OpenAPI Spec generation.
+What goes into the `events` array is very similar. It is also a JSON object that has a `name` and a list of `params`. The difference is that `events` don't have `returns`. Arguments that are passed to the event when it is emitted are in `params`. It also has an optional `description` which can be helpful in OpenAPI Spec generation. Finally, it has an optional `details` object which wraps blockchain specific information about this event. This can be used by the blockchain plugin when invoking this function, and it is also used in documentation generation.
 
 ```json
 {
     "name": "added",
     "description": "An event that occurs when numbers have been added", 
-    "params": []
+    "params": [],
+    "details": {}
 }
 ```
 
@@ -137,7 +139,10 @@ Putting it all together, here is a full example of the FireFly Interface format
                         }
                     }
                 }
-            ]
+            ],
+            "details": {
+                "stateMutability": "viewable"
+            }
         },
         {
             "name": "set",
@@ -154,7 +159,10 @@ Putting it all together, here is a full example of the FireFly Interface format
                     }
                 }
             ],
-            "returns": []
+            "returns": [],
+            "details": {
+                "stateMutability": "payable"
+            }
         }
     ],
     "events": [
@@ -183,7 +191,8 @@ Putting it all together, here is a full example of the FireFly Interface format
                         }
                     }
                 }
-            ]
+            ],
+            "details": {}
         }
     ]
 }
diff --git a/docs/reference/types/contractlistener.md b/docs/reference/types/contractlistener.md
index 1af969f57..8fa0c318a 100644
--- a/docs/reference/types/contractlistener.md
+++ b/docs/reference/types/contractlistener.md
@@ -91,6 +91,7 @@ nav_order: 9
 | `name` | The name of the event | `string` |
 | `description` | A description of the smart contract event | `string` |
 | `params` | An array of event parameter/argument definitions | [`FFIParam[]`](#ffiparam) |
+| `details` | Additional blockchain specific fields about this event from the original smart contract. Used by the blockchain plugin and for documentation generation. | [`JSONObject`](simpletypes#jsonobject) |
 
 ## FFIParam
 
diff --git a/docs/reference/types/ffi.md b/docs/reference/types/ffi.md
index 699a3b0f7..c188540aa 100644
--- a/docs/reference/types/ffi.md
+++ b/docs/reference/types/ffi.md
@@ -49,7 +49,10 @@ nav_order: 8
                         }
                     }
                 }
-            ]
+            ],
+            "details": {
+                "stateMutability": "viewable"
+            }
         },
         {
             "id": "fc6f54ee-2e3c-4e56-b17c-4a1a0ae7394b",
@@ -69,7 +72,10 @@ nav_order: 8
                     }
                 }
             ],
-            "returns": []
+            "returns": [],
+            "details": {
+                "stateMutability": "payable"
+            }
         }
     ],
     "events": [
@@ -132,6 +138,7 @@ nav_order: 8
 | `description` | A description of the smart contract method | `string` |
 | `params` | An array of method parameter/argument definitions | [`FFIParam[]`](#ffiparam) |
 | `returns` | An array of method return definitions | [`FFIParam[]`](#ffiparam) |
+| `details` | Additional blockchain specific fields about this method from the original smart contract. Used by the blockchain plugin and for documentation generation. | [`JSONObject`](simpletypes#jsonobject) |
 
 ## FFIParam
 
@@ -154,6 +161,7 @@ nav_order: 8
 | `name` | The name of the event | `string` |
 | `description` | A description of the smart contract event | `string` |
 | `params` | An array of event parameter/argument definitions | [`FFIParam[]`](#ffiparam) |
+| `details` | Additional blockchain specific fields about this event from the original smart contract. Used by the blockchain plugin and for documentation generation. | [`JSONObject`](simpletypes#jsonobject) |
 
 ## FFIParam
 
diff --git a/docs/swagger/swagger.yaml b/docs/swagger/swagger.yaml
index 9d97fc72c..79e76dc47 100644
--- a/docs/swagger/swagger.yaml
+++ b/docs/swagger/swagger.yaml
@@ -404,6 +404,15 @@ paths:
                         description:
                           description: A description of the smart contract event
                           type: string
+                        details:
+                          additionalProperties:
+                            description: Additional blockchain specific fields about
+                              this event from the original smart contract. Used by
+                              the blockchain plugin and for documentation generation.
+                          description: Additional blockchain specific fields about
+                            this event from the original smart contract. Used by the
+                            blockchain plugin and for documentation generation.
+                          type: object
                         id:
                           description: The UUID of the FFI event definition
                           format: uuid
@@ -466,6 +475,15 @@ paths:
                         description:
                           description: A description of the smart contract method
                           type: string
+                        details:
+                          additionalProperties:
+                            description: Additional blockchain specific fields about
+                              this method from the original smart contract. Used by
+                              the blockchain plugin and for documentation generation.
+                          description: Additional blockchain specific fields about
+                            this method from the original smart contract. Used by
+                            the blockchain plugin and for documentation generation.
+                          type: object
                         id:
                           description: The UUID of the FFI method definition
                           format: uuid
@@ -884,6 +902,15 @@ paths:
                         description:
                           description: A description of the smart contract event
                           type: string
+                        details:
+                          additionalProperties:
+                            description: Additional blockchain specific fields about
+                              this event from the original smart contract. Used by
+                              the blockchain plugin and for documentation generation.
+                          description: Additional blockchain specific fields about
+                            this event from the original smart contract. Used by the
+                            blockchain plugin and for documentation generation.
+                          type: object
                         name:
                           description: The name of the event
                           type: string
@@ -1040,6 +1067,15 @@ paths:
                       description:
                         description: A description of the smart contract event
                         type: string
+                      details:
+                        additionalProperties:
+                          description: Additional blockchain specific fields about
+                            this event from the original smart contract. Used by the
+                            blockchain plugin and for documentation generation.
+                        description: Additional blockchain specific fields about this
+                          event from the original smart contract. Used by the blockchain
+                          plugin and for documentation generation.
+                        type: object
                       name:
                         description: The name of the event
                         type: string
@@ -2042,6 +2078,15 @@ paths:
                           description:
                             description: A description of the smart contract event
                             type: string
+                          details:
+                            additionalProperties:
+                              description: Additional blockchain specific fields about
+                                this event from the original smart contract. Used
+                                by the blockchain plugin and for documentation generation.
+                            description: Additional blockchain specific fields about
+                              this event from the original smart contract. Used by
+                              the blockchain plugin and for documentation generation.
+                            type: object
                           id:
                             description: The UUID of the FFI event definition
                             format: uuid
@@ -2106,6 +2151,15 @@ paths:
                           description:
                             description: A description of the smart contract method
                             type: string
+                          details:
+                            additionalProperties:
+                              description: Additional blockchain specific fields about
+                                this method from the original smart contract. Used
+                                by the blockchain plugin and for documentation generation.
+                            description: Additional blockchain specific fields about
+                              this method from the original smart contract. Used by
+                              the blockchain plugin and for documentation generation.
+                            type: object
                           id:
                             description: The UUID of the FFI method definition
                             format: uuid
@@ -2219,6 +2273,15 @@ paths:
                       description:
                         description: A description of the smart contract event
                         type: string
+                      details:
+                        additionalProperties:
+                          description: Additional blockchain specific fields about
+                            this event from the original smart contract. Used by the
+                            blockchain plugin and for documentation generation.
+                        description: Additional blockchain specific fields about this
+                          event from the original smart contract. Used by the blockchain
+                          plugin and for documentation generation.
+                        type: object
                       name:
                         description: The name of the event
                         type: string
@@ -2250,6 +2313,15 @@ paths:
                       description:
                         description: A description of the smart contract method
                         type: string
+                      details:
+                        additionalProperties:
+                          description: Additional blockchain specific fields about
+                            this method from the original smart contract. Used by
+                            the blockchain plugin and for documentation generation.
+                        description: Additional blockchain specific fields about this
+                          method from the original smart contract. Used by the blockchain
+                          plugin and for documentation generation.
+                        type: object
                       name:
                         description: The name of the method
                         type: string
@@ -2317,6 +2389,15 @@ paths:
                         description:
                           description: A description of the smart contract event
                           type: string
+                        details:
+                          additionalProperties:
+                            description: Additional blockchain specific fields about
+                              this event from the original smart contract. Used by
+                              the blockchain plugin and for documentation generation.
+                          description: Additional blockchain specific fields about
+                            this event from the original smart contract. Used by the
+                            blockchain plugin and for documentation generation.
+                          type: object
                         id:
                           description: The UUID of the FFI event definition
                           format: uuid
@@ -2379,6 +2460,15 @@ paths:
                         description:
                           description: A description of the smart contract method
                           type: string
+                        details:
+                          additionalProperties:
+                            description: Additional blockchain specific fields about
+                              this method from the original smart contract. Used by
+                              the blockchain plugin and for documentation generation.
+                          description: Additional blockchain specific fields about
+                            this method from the original smart contract. Used by
+                            the blockchain plugin and for documentation generation.
+                          type: object
                         id:
                           description: The UUID of the FFI method definition
                           format: uuid
@@ -2496,6 +2586,15 @@ paths:
                         description:
                           description: A description of the smart contract event
                           type: string
+                        details:
+                          additionalProperties:
+                            description: Additional blockchain specific fields about
+                              this event from the original smart contract. Used by
+                              the blockchain plugin and for documentation generation.
+                          description: Additional blockchain specific fields about
+                            this event from the original smart contract. Used by the
+                            blockchain plugin and for documentation generation.
+                          type: object
                         id:
                           description: The UUID of the FFI event definition
                           format: uuid
@@ -2558,6 +2657,15 @@ paths:
                         description:
                           description: A description of the smart contract method
                           type: string
+                        details:
+                          additionalProperties:
+                            description: Additional blockchain specific fields about
+                              this method from the original smart contract. Used by
+                              the blockchain plugin and for documentation generation.
+                          description: Additional blockchain specific fields about
+                            this method from the original smart contract. Used by
+                            the blockchain plugin and for documentation generation.
+                          type: object
                         id:
                           description: The UUID of the FFI method definition
                           format: uuid
@@ -2681,6 +2789,15 @@ paths:
                         description:
                           description: A description of the smart contract event
                           type: string
+                        details:
+                          additionalProperties:
+                            description: Additional blockchain specific fields about
+                              this event from the original smart contract. Used by
+                              the blockchain plugin and for documentation generation.
+                          description: Additional blockchain specific fields about
+                            this event from the original smart contract. Used by the
+                            blockchain plugin and for documentation generation.
+                          type: object
                         id:
                           description: The UUID of the FFI event definition
                           format: uuid
@@ -2743,6 +2860,15 @@ paths:
                         description:
                           description: A description of the smart contract method
                           type: string
+                        details:
+                          additionalProperties:
+                            description: Additional blockchain specific fields about
+                              this method from the original smart contract. Used by
+                              the blockchain plugin and for documentation generation.
+                          description: Additional blockchain specific fields about
+                            this method from the original smart contract. Used by
+                            the blockchain plugin and for documentation generation.
+                          type: object
                         id:
                           description: The UUID of the FFI method definition
                           format: uuid
@@ -2873,6 +2999,15 @@ paths:
                         description:
                           description: A description of the smart contract event
                           type: string
+                        details:
+                          additionalProperties:
+                            description: Additional blockchain specific fields about
+                              this event from the original smart contract. Used by
+                              the blockchain plugin and for documentation generation.
+                          description: Additional blockchain specific fields about
+                            this event from the original smart contract. Used by the
+                            blockchain plugin and for documentation generation.
+                          type: object
                         id:
                           description: The UUID of the FFI event definition
                           format: uuid
@@ -2935,6 +3070,15 @@ paths:
                         description:
                           description: A description of the smart contract method
                           type: string
+                        details:
+                          additionalProperties:
+                            description: Additional blockchain specific fields about
+                              this method from the original smart contract. Used by
+                              the blockchain plugin and for documentation generation.
+                          description: Additional blockchain specific fields about
+                            this method from the original smart contract. Used by
+                            the blockchain plugin and for documentation generation.
+                          type: object
                         id:
                           description: The UUID of the FFI method definition
                           format: uuid
@@ -3067,6 +3211,15 @@ paths:
                     description:
                       description: A description of the smart contract method
                       type: string
+                    details:
+                      additionalProperties:
+                        description: Additional blockchain specific fields about this
+                          method from the original smart contract. Used by the blockchain
+                          plugin and for documentation generation.
+                      description: Additional blockchain specific fields about this
+                        method from the original smart contract. Used by the blockchain
+                        plugin and for documentation generation.
+                      type: object
                     name:
                       description: The name of the method
                       type: string
@@ -3384,6 +3537,15 @@ paths:
                         description:
                           description: A description of the smart contract event
                           type: string
+                        details:
+                          additionalProperties:
+                            description: Additional blockchain specific fields about
+                              this event from the original smart contract. Used by
+                              the blockchain plugin and for documentation generation.
+                          description: Additional blockchain specific fields about
+                            this event from the original smart contract. Used by the
+                            blockchain plugin and for documentation generation.
+                          type: object
                         name:
                           description: The name of the event
                           type: string
@@ -3488,6 +3650,15 @@ paths:
                     description:
                       description: A description of the smart contract event
                       type: string
+                    details:
+                      additionalProperties:
+                        description: Additional blockchain specific fields about this
+                          event from the original smart contract. Used by the blockchain
+                          plugin and for documentation generation.
+                      description: Additional blockchain specific fields about this
+                        event from the original smart contract. Used by the blockchain
+                        plugin and for documentation generation.
+                      type: object
                     name:
                       description: The name of the event
                       type: string
@@ -3576,6 +3747,15 @@ paths:
                       description:
                         description: A description of the smart contract event
                         type: string
+                      details:
+                        additionalProperties:
+                          description: Additional blockchain specific fields about
+                            this event from the original smart contract. Used by the
+                            blockchain plugin and for documentation generation.
+                        description: Additional blockchain specific fields about this
+                          event from the original smart contract. Used by the blockchain
+                          plugin and for documentation generation.
+                        type: object
                       name:
                         description: The name of the event
                         type: string
@@ -3721,6 +3901,15 @@ paths:
                       description:
                         description: A description of the smart contract event
                         type: string
+                      details:
+                        additionalProperties:
+                          description: Additional blockchain specific fields about
+                            this event from the original smart contract. Used by the
+                            blockchain plugin and for documentation generation.
+                        description: Additional blockchain specific fields about this
+                          event from the original smart contract. Used by the blockchain
+                          plugin and for documentation generation.
+                        type: object
                       name:
                         description: The name of the event
                         type: string
@@ -3851,6 +4040,15 @@ paths:
                     description:
                       description: A description of the smart contract method
                       type: string
+                    details:
+                      additionalProperties:
+                        description: Additional blockchain specific fields about this
+                          method from the original smart contract. Used by the blockchain
+                          plugin and for documentation generation.
+                      description: Additional blockchain specific fields about this
+                        method from the original smart contract. Used by the blockchain
+                        plugin and for documentation generation.
+                      type: object
                     name:
                       description: The name of the method
                       type: string
@@ -9196,6 +9394,15 @@ paths:
                         description:
                           description: A description of the smart contract event
                           type: string
+                        details:
+                          additionalProperties:
+                            description: Additional blockchain specific fields about
+                              this event from the original smart contract. Used by
+                              the blockchain plugin and for documentation generation.
+                          description: Additional blockchain specific fields about
+                            this event from the original smart contract. Used by the
+                            blockchain plugin and for documentation generation.
+                          type: object
                         id:
                           description: The UUID of the FFI event definition
                           format: uuid
@@ -9258,6 +9465,15 @@ paths:
                         description:
                           description: A description of the smart contract method
                           type: string
+                        details:
+                          additionalProperties:
+                            description: Additional blockchain specific fields about
+                              this method from the original smart contract. Used by
+                              the blockchain plugin and for documentation generation.
+                          description: Additional blockchain specific fields about
+                            this method from the original smart contract. Used by
+                            the blockchain plugin and for documentation generation.
+                          type: object
                         id:
                           description: The UUID of the FFI method definition
                           format: uuid
@@ -9411,6 +9627,15 @@ paths:
                     description:
                       description: A description of the smart contract method
                       type: string
+                    details:
+                      additionalProperties:
+                        description: Additional blockchain specific fields about this
+                          method from the original smart contract. Used by the blockchain
+                          plugin and for documentation generation.
+                      description: Additional blockchain specific fields about this
+                        method from the original smart contract. Used by the blockchain
+                        plugin and for documentation generation.
+                      type: object
                     name:
                       description: The name of the method
                       type: string
@@ -9748,6 +9973,15 @@ paths:
                         description:
                           description: A description of the smart contract event
                           type: string
+                        details:
+                          additionalProperties:
+                            description: Additional blockchain specific fields about
+                              this event from the original smart contract. Used by
+                              the blockchain plugin and for documentation generation.
+                          description: Additional blockchain specific fields about
+                            this event from the original smart contract. Used by the
+                            blockchain plugin and for documentation generation.
+                          type: object
                         name:
                           description: The name of the event
                           type: string
@@ -9872,6 +10106,15 @@ paths:
                     description:
                       description: A description of the smart contract event
                       type: string
+                    details:
+                      additionalProperties:
+                        description: Additional blockchain specific fields about this
+                          event from the original smart contract. Used by the blockchain
+                          plugin and for documentation generation.
+                      description: Additional blockchain specific fields about this
+                        event from the original smart contract. Used by the blockchain
+                        plugin and for documentation generation.
+                      type: object
                     name:
                       description: The name of the event
                       type: string
@@ -9955,6 +10198,15 @@ paths:
                       description:
                         description: A description of the smart contract event
                         type: string
+                      details:
+                        additionalProperties:
+                          description: Additional blockchain specific fields about
+                            this event from the original smart contract. Used by the
+                            blockchain plugin and for documentation generation.
+                        description: Additional blockchain specific fields about this
+                          event from the original smart contract. Used by the blockchain
+                          plugin and for documentation generation.
+                        type: object
                       name:
                         description: The name of the event
                         type: string
@@ -10106,6 +10358,15 @@ paths:
                     description:
                       description: A description of the smart contract method
                       type: string
+                    details:
+                      additionalProperties:
+                        description: Additional blockchain specific fields about this
+                          method from the original smart contract. Used by the blockchain
+                          plugin and for documentation generation.
+                      description: Additional blockchain specific fields about this
+                        method from the original smart contract. Used by the blockchain
+                        plugin and for documentation generation.
+                      type: object
                     name:
                       description: The name of the method
                       type: string
@@ -11071,6 +11332,15 @@ paths:
                           description:
                             description: A description of the smart contract event
                             type: string
+                          details:
+                            additionalProperties:
+                              description: Additional blockchain specific fields about
+                                this event from the original smart contract. Used
+                                by the blockchain plugin and for documentation generation.
+                            description: Additional blockchain specific fields about
+                              this event from the original smart contract. Used by
+                              the blockchain plugin and for documentation generation.
+                            type: object
                           id:
                             description: The UUID of the FFI event definition
                             format: uuid
@@ -11135,6 +11405,15 @@ paths:
                           description:
                             description: A description of the smart contract method
                             type: string
+                          details:
+                            additionalProperties:
+                              description: Additional blockchain specific fields about
+                                this method from the original smart contract. Used
+                                by the blockchain plugin and for documentation generation.
+                            description: Additional blockchain specific fields about
+                              this method from the original smart contract. Used by
+                              the blockchain plugin and for documentation generation.
+                            type: object
                           id:
                             description: The UUID of the FFI method definition
                             format: uuid
@@ -11255,6 +11534,15 @@ paths:
                       description:
                         description: A description of the smart contract event
                         type: string
+                      details:
+                        additionalProperties:
+                          description: Additional blockchain specific fields about
+                            this event from the original smart contract. Used by the
+                            blockchain plugin and for documentation generation.
+                        description: Additional blockchain specific fields about this
+                          event from the original smart contract. Used by the blockchain
+                          plugin and for documentation generation.
+                        type: object
                       name:
                         description: The name of the event
                         type: string
@@ -11286,6 +11574,15 @@ paths:
                       description:
                         description: A description of the smart contract method
                         type: string
+                      details:
+                        additionalProperties:
+                          description: Additional blockchain specific fields about
+                            this method from the original smart contract. Used by
+                            the blockchain plugin and for documentation generation.
+                        description: Additional blockchain specific fields about this
+                          method from the original smart contract. Used by the blockchain
+                          plugin and for documentation generation.
+                        type: object
                       name:
                         description: The name of the method
                         type: string
@@ -11353,6 +11650,15 @@ paths:
                         description:
                           description: A description of the smart contract event
                           type: string
+                        details:
+                          additionalProperties:
+                            description: Additional blockchain specific fields about
+                              this event from the original smart contract. Used by
+                              the blockchain plugin and for documentation generation.
+                          description: Additional blockchain specific fields about
+                            this event from the original smart contract. Used by the
+                            blockchain plugin and for documentation generation.
+                          type: object
                         id:
                           description: The UUID of the FFI event definition
                           format: uuid
@@ -11415,6 +11721,15 @@ paths:
                         description:
                           description: A description of the smart contract method
                           type: string
+                        details:
+                          additionalProperties:
+                            description: Additional blockchain specific fields about
+                              this method from the original smart contract. Used by
+                              the blockchain plugin and for documentation generation.
+                          description: Additional blockchain specific fields about
+                            this method from the original smart contract. Used by
+                            the blockchain plugin and for documentation generation.
+                          type: object
                         id:
                           description: The UUID of the FFI method definition
                           format: uuid
@@ -11539,6 +11854,15 @@ paths:
                         description:
                           description: A description of the smart contract event
                           type: string
+                        details:
+                          additionalProperties:
+                            description: Additional blockchain specific fields about
+                              this event from the original smart contract. Used by
+                              the blockchain plugin and for documentation generation.
+                          description: Additional blockchain specific fields about
+                            this event from the original smart contract. Used by the
+                            blockchain plugin and for documentation generation.
+                          type: object
                         id:
                           description: The UUID of the FFI event definition
                           format: uuid
@@ -11601,6 +11925,15 @@ paths:
                         description:
                           description: A description of the smart contract method
                           type: string
+                        details:
+                          additionalProperties:
+                            description: Additional blockchain specific fields about
+                              this method from the original smart contract. Used by
+                              the blockchain plugin and for documentation generation.
+                          description: Additional blockchain specific fields about
+                            this method from the original smart contract. Used by
+                            the blockchain plugin and for documentation generation.
+                          type: object
                         id:
                           description: The UUID of the FFI method definition
                           format: uuid
@@ -11731,6 +12064,15 @@ paths:
                         description:
                           description: A description of the smart contract event
                           type: string
+                        details:
+                          additionalProperties:
+                            description: Additional blockchain specific fields about
+                              this event from the original smart contract. Used by
+                              the blockchain plugin and for documentation generation.
+                          description: Additional blockchain specific fields about
+                            this event from the original smart contract. Used by the
+                            blockchain plugin and for documentation generation.
+                          type: object
                         id:
                           description: The UUID of the FFI event definition
                           format: uuid
@@ -11793,6 +12135,15 @@ paths:
                         description:
                           description: A description of the smart contract method
                           type: string
+                        details:
+                          additionalProperties:
+                            description: Additional blockchain specific fields about
+                              this method from the original smart contract. Used by
+                              the blockchain plugin and for documentation generation.
+                          description: Additional blockchain specific fields about
+                            this method from the original smart contract. Used by
+                            the blockchain plugin and for documentation generation.
+                          type: object
                         id:
                           description: The UUID of the FFI method definition
                           format: uuid
@@ -11930,6 +12281,15 @@ paths:
                         description:
                           description: A description of the smart contract event
                           type: string
+                        details:
+                          additionalProperties:
+                            description: Additional blockchain specific fields about
+                              this event from the original smart contract. Used by
+                              the blockchain plugin and for documentation generation.
+                          description: Additional blockchain specific fields about
+                            this event from the original smart contract. Used by the
+                            blockchain plugin and for documentation generation.
+                          type: object
                         id:
                           description: The UUID of the FFI event definition
                           format: uuid
@@ -11992,6 +12352,15 @@ paths:
                         description:
                           description: A description of the smart contract method
                           type: string
+                        details:
+                          additionalProperties:
+                            description: Additional blockchain specific fields about
+                              this method from the original smart contract. Used by
+                              the blockchain plugin and for documentation generation.
+                          description: Additional blockchain specific fields about
+                            this method from the original smart contract. Used by
+                            the blockchain plugin and for documentation generation.
+                          type: object
                         id:
                           description: The UUID of the FFI method definition
                           format: uuid
@@ -12131,6 +12500,15 @@ paths:
                     description:
                       description: A description of the smart contract method
                       type: string
+                    details:
+                      additionalProperties:
+                        description: Additional blockchain specific fields about this
+                          method from the original smart contract. Used by the blockchain
+                          plugin and for documentation generation.
+                      description: Additional blockchain specific fields about this
+                        method from the original smart contract. Used by the blockchain
+                        plugin and for documentation generation.
+                      type: object
                     name:
                       description: The name of the method
                       type: string
@@ -12455,6 +12833,15 @@ paths:
                         description:
                           description: A description of the smart contract event
                           type: string
+                        details:
+                          additionalProperties:
+                            description: Additional blockchain specific fields about
+                              this event from the original smart contract. Used by
+                              the blockchain plugin and for documentation generation.
+                          description: Additional blockchain specific fields about
+                            this event from the original smart contract. Used by the
+                            blockchain plugin and for documentation generation.
+                          type: object
                         name:
                           description: The name of the event
                           type: string
@@ -12566,6 +12953,15 @@ paths:
                     description:
                       description: A description of the smart contract event
                       type: string
+                    details:
+                      additionalProperties:
+                        description: Additional blockchain specific fields about this
+                          event from the original smart contract. Used by the blockchain
+                          plugin and for documentation generation.
+                      description: Additional blockchain specific fields about this
+                        event from the original smart contract. Used by the blockchain
+                        plugin and for documentation generation.
+                      type: object
                     name:
                       description: The name of the event
                       type: string
@@ -12654,6 +13050,15 @@ paths:
                       description:
                         description: A description of the smart contract event
                         type: string
+                      details:
+                        additionalProperties:
+                          description: Additional blockchain specific fields about
+                            this event from the original smart contract. Used by the
+                            blockchain plugin and for documentation generation.
+                        description: Additional blockchain specific fields about this
+                          event from the original smart contract. Used by the blockchain
+                          plugin and for documentation generation.
+                        type: object
                       name:
                         description: The name of the event
                         type: string
@@ -12813,6 +13218,15 @@ paths:
                       description:
                         description: A description of the smart contract event
                         type: string
+                      details:
+                        additionalProperties:
+                          description: Additional blockchain specific fields about
+                            this event from the original smart contract. Used by the
+                            blockchain plugin and for documentation generation.
+                        description: Additional blockchain specific fields about this
+                          event from the original smart contract. Used by the blockchain
+                          plugin and for documentation generation.
+                        type: object
                       name:
                         description: The name of the event
                         type: string
@@ -12950,6 +13364,15 @@ paths:
                     description:
                       description: A description of the smart contract method
                       type: string
+                    details:
+                      additionalProperties:
+                        description: Additional blockchain specific fields about this
+                          method from the original smart contract. Used by the blockchain
+                          plugin and for documentation generation.
+                      description: Additional blockchain specific fields about this
+                        method from the original smart contract. Used by the blockchain
+                        plugin and for documentation generation.
+                      type: object
                     name:
                       description: The name of the method
                       type: string
@@ -18194,8 +18617,7 @@ paths:
   /namespaces/{ns}/network/identities/{did}:
     get:
       deprecated: true
-      description: Gets an identity by its DID (deprecated - use /identities/{did}
-        instead of /network/identities/{did})
+      description: Gets an identity by its DID
       operationId: getNetworkIdentityByDIDNamespace
       parameters:
       - description: The identity DID
@@ -25673,8 +26095,7 @@ paths:
   /network/identities/{did}:
     get:
       deprecated: true
-      description: Gets an identity by its DID (deprecated - use /identities/{did}
-        instead of /network/identities/{did})
+      description: Gets an identity by its DID
       operationId: getNetworkIdentityByDID
       parameters:
       - description: The identity DID
diff --git a/docs/tutorials/custom_contracts/ethereum.md b/docs/tutorials/custom_contracts/ethereum.md
index cea0aa2e7..8e4d00061 100644
--- a/docs/tutorials/custom_contracts/ethereum.md
+++ b/docs/tutorials/custom_contracts/ethereum.md
@@ -517,6 +517,35 @@ To make a read-only request to the blockchain to check the current value of the
 
 > **NOTE:** Some contracts may have queries that require input parameters. That's why the query endpoint is a `POST`, rather than a `GET` so that parameters can be passed as JSON in the request body. This particular function does not have any parameters, so we just pass an empty JSON object.
 
+## Passing additional options with a request
+
+Some smart contract functions may accept or require additional options to be passed with the request. For example, a Solidity function might be `payable`, meaning that a `value` field must be specified, indicating an amount of ETH to be transferred with the request. Each of your smart contract API's `/invoke` or `/query` endpoints support an `options` object in addition to the `input` arguments for the function itself.
+
+Here is an example of sending 100 wei with a transaction:
+
+### Request
+
+`POST` `http://localhost:5000/api/v1/namespaces/default/apis/simple-storage/invoke/set`
+
+```json
+{
+  "input": {
+    "newValue": 3
+  },
+  "options": {
+    "value": 100
+  }
+}
+```
+
+### Response
+
+```json
+{
+  "id": "41c67c63-52cf-47ce-8a59-895fe2ffdc86"
+}
+```
+
 ## Create a blockchain event listener
 
 Now that we've seen how to submit transactions and preform read-only queries to the blockchain, let's look at how to receive blockchain events so we know when things are happening in realtime.
diff --git a/go.mod b/go.mod
index 60cbe046b..46d127585 100644
--- a/go.mod
+++ b/go.mod
@@ -13,7 +13,8 @@ require (
 	github.com/golang-migrate/migrate/v4 v4.15.2
 	github.com/gorilla/mux v1.8.0
 	github.com/gorilla/websocket v1.5.0
-	github.com/hyperledger/firefly-common v0.1.9
+	github.com/hyperledger/firefly-common v0.1.10
+	github.com/hyperledger/firefly-signer v0.9.10
 	github.com/jarcoal/httpmock v1.1.0
 	github.com/karlseguin/ccache v2.0.3+incompatible
 	github.com/lib/pq v1.10.6
@@ -46,7 +47,6 @@ require (
 	github.com/inconshreveable/mousetrap v1.0.0 // indirect
 	github.com/invopop/yaml v0.1.0 // indirect
 	github.com/josharian/intern v1.0.0 // indirect
-	github.com/karlseguin/expect v1.0.8 // indirect
 	github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
 	github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
 	github.com/magiconair/properties v1.8.6 // indirect
@@ -72,6 +72,7 @@ require (
 	github.com/stretchr/objx v0.2.0 // indirect
 	github.com/subosito/gotenv v1.4.0 // indirect
 	github.com/wayneashleyberry/terminal-dimensions v1.0.0 // indirect
+	github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0 // indirect
 	github.com/x-cray/logrus-prefixed-formatter v0.5.2 // indirect
 	go.uber.org/atomic v1.9.0 // indirect
 	golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect
diff --git a/go.sum b/go.sum
index f4441f95a..758f8d0bc 100644
--- a/go.sum
+++ b/go.sum
@@ -123,6 +123,7 @@ github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt
 github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
 github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
 github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
+github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
 github.com/aidarkhanov/nanoid v1.0.8 h1:yxyJkgsEDFXP7+97vc6JevMcjyb03Zw+/9fqhlVXBXA=
 github.com/aidarkhanov/nanoid v1.0.8/go.mod h1:vadfZHT+m4uDhttg0yY4wW3GKtl2T6i4d2Age+45pYk=
 github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
@@ -190,6 +191,19 @@ github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnweb
 github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
 github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
 github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=
+github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
+github.com/btcsuite/btcd v0.22.1/go.mod h1:wqgTSL29+50LRkmOVknEdmt8ZojIzhuWvgu/iptuN7Y=
+github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
+github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
+github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
+github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o=
+github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
+github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
+github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I=
+github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
+github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
+github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
+github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
 github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
 github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
 github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
@@ -366,9 +380,11 @@ github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1S
 github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s=
 github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8=
 github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I=
+github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218=
 github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
 github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0=
 github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
@@ -432,6 +448,7 @@ github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3
 github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
 github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
 github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
 github.com/fsouza/fake-gcs-server v1.17.0/go.mod h1:D1rTE4YCyHFNa99oyJJ5HyclvN/0uQR+pM/VdlL83bw=
@@ -710,8 +727,11 @@ github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/J
 github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
 github.com/hashicorp/serf v0.9.7/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
-github.com/hyperledger/firefly-common v0.1.9 h1:7QWaJ6WU5NrLp+lKqkWpLi/T+HAJn4tk84tq/cxpghk=
-github.com/hyperledger/firefly-common v0.1.9/go.mod h1:MYL6Dbj3KqM/79IkS+mCzJ7wRguNbd/PKdVu8aXo5TI=
+github.com/hyperledger/firefly-common v0.1.5/go.mod h1:qGy7i8eWlE8Ed7jFn2Hpn8bYaflL204j4NJB1mAfTms=
+github.com/hyperledger/firefly-common v0.1.10 h1:BtP20uuPMP9+Q1+bRPJyBDCp5hObqd+JVMqoAoWBvVw=
+github.com/hyperledger/firefly-common v0.1.10/go.mod h1:MYL6Dbj3KqM/79IkS+mCzJ7wRguNbd/PKdVu8aXo5TI=
+github.com/hyperledger/firefly-signer v0.9.10 h1:pJgeuAH+q2qLuSSJ4sW3eZ9rd7oEyssCKDBNMj905As=
+github.com/hyperledger/firefly-signer v0.9.10/go.mod h1:BjzIWMj4e1Em52liuyCV6dQhPeVc3WOuLOnEDGS4QK4=
 github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
@@ -772,6 +792,8 @@ github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dv
 github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
 github.com/jarcoal/httpmock v1.1.0 h1:F47ChZj1Y2zFsCXxNkBPwNNKnAyOATcdQibk0qEdVCE=
 github.com/jarcoal/httpmock v1.1.0/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
+github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
+github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
 github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
 github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
 github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
@@ -788,6 +810,7 @@ github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUB
 github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
 github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
 github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
+github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
@@ -815,6 +838,7 @@ github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvW
 github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
 github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
 github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
 github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
 github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
@@ -962,6 +986,7 @@ github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:v
 github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@@ -976,6 +1001,8 @@ github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042
 github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
 github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
 github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
+github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
+github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
@@ -1022,8 +1049,10 @@ github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/9
 github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
 github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
 github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
+github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
 github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
 github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
+github.com/pelletier/go-toml/v2 v2.0.0-beta.8/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
 github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU=
 github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
 github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
@@ -1111,6 +1140,7 @@ github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfF
 github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
 github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
 github.com/safchain/ethtool v0.0.0-20210803160452-9aa261dae9b1/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
+github.com/sagikazarmark/crypt v0.5.0/go.mod h1:l+nzl7KWh51rpzp2h7t4MZWyiEWdhNpOAnclKvg+mdA=
 github.com/sagikazarmark/crypt v0.6.0/go.mod h1:U8+INwJo3nBv1m6A/8OBXAq7Jnpspk5AxSgDyEQcea8=
 github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 h1:TToq11gyfNlrMFZiYujSekIsPd9AmsA2Bj/iv+s4JHE=
 github.com/santhosh-tekuri/jsonschema/v5 v5.0.0/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0=
@@ -1148,6 +1178,7 @@ github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z
 github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo=
 github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo=
 github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
 github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
 github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
@@ -1167,6 +1198,7 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
 github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
+github.com/spf13/viper v1.11.0/go.mod h1:djo0X/bA5+tYVoCn+C7cAYJGcVn/qYLFTG8gdUsX7Zk=
 github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ=
 github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI=
 github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8=
@@ -1248,10 +1280,13 @@ go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
 go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
 go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg=
 go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
+go.etcd.io/etcd/api/v3 v3.5.2/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=
 go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=
 go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
+go.etcd.io/etcd/client/pkg/v3 v3.5.2/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
 go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
 go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
+go.etcd.io/etcd/client/v2 v2.305.2/go.mod h1:2D7ZejHVMIfog1221iLSYlQRzrtECw3kz4I4VAQm3qI=
 go.etcd.io/etcd/client/v2 v2.305.4/go.mod h1:Ud+VUwIi9/uQHOMA+4ekToJ12lTxlv0zB/+DHwTGEbU=
 go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0=
 go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY=
@@ -1303,6 +1338,7 @@ go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9E
 go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
+golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@@ -1319,8 +1355,10 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U
 golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
@@ -1331,6 +1369,7 @@ golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5y
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM=
 golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -1381,6 +1420,7 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
 golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -1634,6 +1674,7 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn
 golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.0.0-20220411215600-e5f449aeb171/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 h1:CBpWXWQpIRjzmkkA+M7q9Fqnwd2mZr3AFqexg8YTfoM=
 golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
diff --git a/internal/apiserver/ffi2swagger.go b/internal/apiserver/ffi2swagger.go
index 844cf2ef8..e69209bc0 100644
--- a/internal/apiserver/ffi2swagger.go
+++ b/internal/apiserver/ffi2swagger.go
@@ -21,10 +21,16 @@ import (
 	"encoding/json"
 	"fmt"
 	"net/http"
+	"sort"
+	"strings"
 
 	"github.com/getkin/kin-openapi/openapi3"
+	"github.com/hyperledger/firefly-common/pkg/config"
 	"github.com/hyperledger/firefly-common/pkg/ffapi"
 	"github.com/hyperledger/firefly-common/pkg/fftypes"
+	"github.com/hyperledger/firefly-common/pkg/i18n"
+	"github.com/hyperledger/firefly/internal/coreconfig"
+	"github.com/hyperledger/firefly/internal/coremsgs"
 	"github.com/hyperledger/firefly/pkg/core"
 	"github.com/hyperledger/firefly/pkg/database"
 )
@@ -73,34 +79,49 @@ func (og *ffiSwaggerGen) Generate(ctx context.Context, baseURL string, api *core
 	}
 
 	return ffapi.NewSwaggerGen(&ffapi.Options{
-		Title:       ffi.Name,
-		Version:     ffi.Version,
-		Description: ffi.Description,
-		BaseURL:     baseURL,
+		Title:                 ffi.Name,
+		Version:               ffi.Version,
+		Description:           ffi.Description,
+		BaseURL:               baseURL,
+		DefaultRequestTimeout: config.GetDuration(coreconfig.APIRequestTimeout),
 	}).Generate(ctx, routes)
 }
 
 func (og *ffiSwaggerGen) addMethod(routes []*ffapi.Route, method *core.FFIMethod, hasLocation bool) []*ffapi.Route {
+	ctx := context.Background()
+	description := method.Description
+	if method.Details != nil && len(method.Details) > 0 {
+		additionalDetailsHeader := i18n.Expand(ctx, coremsgs.APISmartContractDetails)
+		description = fmt.Sprintf("%s\n\n%s:\n\n%s", description, additionalDetailsHeader, buildDetailsTable(ctx, method.Details))
+	}
 	routes = append(routes, &ffapi.Route{
-		Name:             fmt.Sprintf("invoke_%s", method.Pathname),
-		Path:             fmt.Sprintf("invoke/%s", method.Pathname), // must match a route defined in apiserver routes!
-		Method:           http.MethodPost,
-		JSONInputSchema:  func(ctx context.Context) string { return contractCallJSONSchema(&method.Params, hasLocation).String() },
-		JSONOutputSchema: func(ctx context.Context) string { return ffiParamsJSONSchema(&method.Returns).String() },
-		JSONOutputCodes:  []int{http.StatusOK},
+		Name:                     fmt.Sprintf("invoke_%s", method.Pathname),
+		Path:                     fmt.Sprintf("invoke/%s", method.Pathname), // must match a route defined in apiserver routes!
+		Method:                   http.MethodPost,
+		JSONInputSchema:          func(ctx context.Context) string { return contractCallJSONSchema(&method.Params, hasLocation).String() },
+		JSONOutputSchema:         func(ctx context.Context) string { return ffiParamsJSONSchema(&method.Returns).String() },
+		JSONOutputCodes:          []int{http.StatusOK},
+		PreTranslatedDescription: description,
 	})
 	routes = append(routes, &ffapi.Route{
-		Name:             fmt.Sprintf("query_%s", method.Pathname),
-		Path:             fmt.Sprintf("query/%s", method.Pathname), // must match a route defined in apiserver routes!
-		Method:           http.MethodPost,
-		JSONInputSchema:  func(ctx context.Context) string { return contractCallJSONSchema(&method.Params, hasLocation).String() },
-		JSONOutputSchema: func(ctx context.Context) string { return ffiParamsJSONSchema(&method.Returns).String() },
-		JSONOutputCodes:  []int{http.StatusOK},
+		Name:                     fmt.Sprintf("query_%s", method.Pathname),
+		Path:                     fmt.Sprintf("query/%s", method.Pathname), // must match a route defined in apiserver routes!
+		Method:                   http.MethodPost,
+		JSONInputSchema:          func(ctx context.Context) string { return contractCallJSONSchema(&method.Params, hasLocation).String() },
+		JSONOutputSchema:         func(ctx context.Context) string { return ffiParamsJSONSchema(&method.Returns).String() },
+		JSONOutputCodes:          []int{http.StatusOK},
+		PreTranslatedDescription: description,
 	})
 	return routes
 }
 
 func (og *ffiSwaggerGen) addEvent(routes []*ffapi.Route, event *core.FFIEvent, hasLocation bool) []*ffapi.Route {
+	ctx := context.Background()
+	description := event.Description
+	if event.Details != nil && len(event.Details) > 0 {
+		additionalDetailsHeader := i18n.Expand(ctx, coremsgs.APISmartContractDetails)
+		description = fmt.Sprintf("%s\n\n%s:\n\n%s", description, additionalDetailsHeader, buildDetailsTable(ctx, event.Details))
+	}
 	routes = append(routes, &ffapi.Route{
 		Name:   fmt.Sprintf("createlistener_%s", event.Pathname),
 		Path:   fmt.Sprintf("listeners/%s", event.Pathname), // must match a route defined in apiserver routes!
@@ -111,8 +132,9 @@ func (og *ffiSwaggerGen) addEvent(routes []*ffapi.Route, event *core.FFIEvent, h
 			}
 			return &ContractListenerInputWithLocation{}
 		},
-		JSONOutputValue: func() interface{} { return &core.ContractListener{} },
-		JSONOutputCodes: []int{http.StatusOK},
+		JSONOutputValue:          func() interface{} { return &core.ContractListener{} },
+		JSONOutputCodes:          []int{http.StatusOK},
+		PreTranslatedDescription: description,
 	})
 	routes = append(routes, &ffapi.Route{
 		Name:            fmt.Sprintf("getlistener_%s", event.Pathname),
@@ -133,15 +155,18 @@ func (og *ffiSwaggerGen) addEvent(routes []*ffapi.Route, event *core.FFIEvent, h
  * Returns the JSON Schema as an `fftypes.JSONObject`.
  */
 func contractCallJSONSchema(params *core.FFIParams, hasLocation bool) *fftypes.JSONObject {
-	req := &core.ContractCallRequest{
-		Input: *ffiParamsJSONSchema(params),
+	properties := fftypes.JSONObject{
+		"input": ffiParamsJSONSchema(params),
+		"options": fftypes.JSONObject{
+			"type": "object",
+		},
 	}
 	if !hasLocation {
-		req.Location = fftypes.JSONAnyPtr(`{}`)
+		properties["location"] = fftypes.JSONAnyPtr(`{}`)
 	}
 	return &fftypes.JSONObject{
 		"type":       "object",
-		"properties": req,
+		"properties": properties,
 	}
 }
 
@@ -163,3 +188,20 @@ func ffiParamJSONSchema(param *core.FFIParam) *fftypes.JSONObject {
 	}
 	return nil
 }
+
+func buildDetailsTable(ctx context.Context, details map[string]interface{}) string {
+	keyHeader := i18n.Expand(ctx, coremsgs.APISmartContractDetailsKey)
+	valueHeader := i18n.Expand(ctx, coremsgs.APISmartContractDetailsKey)
+	var s strings.Builder
+	s.WriteString(fmt.Sprintf("| %s | %s |\n|-----|-------|\n", keyHeader, valueHeader))
+	keys := make([]string, len(details))
+	i := 0
+	for key := range details {
+		keys[i] = key
+	}
+	sort.Strings(keys)
+	for _, key := range keys {
+		s.WriteString(fmt.Sprintf("|%s|%s|\n", key, details[key]))
+	}
+	return s.String()
+}
diff --git a/internal/apiserver/route_get_net_did.go b/internal/apiserver/route_get_net_did.go
index 55d981c8d..5f04259f9 100644
--- a/internal/apiserver/route_get_net_did.go
+++ b/internal/apiserver/route_get_net_did.go
@@ -35,7 +35,7 @@ var getNetworkIdentityByDID = &ffapi.Route{
 	PathParams: []*ffapi.PathParam{
 		{Name: "did", Description: coremsgs.APIParamsDID},
 	},
-	Description:     coremsgs.APIEndpointsGetNetworkIdentityByDID,
+	Description:     coremsgs.APIEndpointsGetIdentityByDID,
 	Deprecated:      true, // use getIdentityByDID instead
 	JSONInputValue:  nil,
 	JSONOutputValue: func() interface{} { return &core.IdentityWithVerifiers{} },
diff --git a/internal/apiserver/route_post_contract_query.go b/internal/apiserver/route_post_contract_query.go
index d5e6e8068..c74dc052f 100644
--- a/internal/apiserver/route_post_contract_query.go
+++ b/internal/apiserver/route_post_contract_query.go
@@ -29,8 +29,8 @@ var postContractQuery = &ffapi.Route{
 	Path:            "contracts/query",
 	Method:          http.MethodPost,
 	PathParams:      nil,
-	QueryParams:     []*ffapi.QueryParam{},
 	Description:     coremsgs.APIEndpointsPostContractQuery,
+	QueryParams:     []*ffapi.QueryParam{},
 	JSONInputValue:  func() interface{} { return &core.ContractCallRequest{} },
 	JSONOutputValue: func() interface{} { return make(map[string]interface{}) },
 	JSONOutputCodes: []int{http.StatusOK},
diff --git a/internal/apiserver/route_spi_get_namespace_by_name.go b/internal/apiserver/route_spi_get_namespace_by_name.go
index 12e22ca29..3b42cdef9 100644
--- a/internal/apiserver/route_spi_get_namespace_by_name.go
+++ b/internal/apiserver/route_spi_get_namespace_by_name.go
@@ -32,7 +32,7 @@ var spiGetNamespaceByName = &ffapi.Route{
 		{Name: "ns", Description: coremsgs.APIParamsNamespace},
 	},
 	QueryParams:     nil,
-	Description:     coremsgs.APIEndpointsAdminGetNamespaceByName,
+	Description:     coremsgs.APIEndpointsPostNewNamespace,
 	JSONInputValue:  nil,
 	JSONOutputValue: func() interface{} { return &core.Namespace{} },
 	JSONOutputCodes: []int{http.StatusOK},
diff --git a/internal/blockchain/ethereum/abi_definitions.go b/internal/blockchain/ethereum/abi_definitions.go
index a0849f00a..e86b540b3 100644
--- a/internal/blockchain/ethereum/abi_definitions.go
+++ b/internal/blockchain/ethereum/abi_definitions.go
@@ -16,10 +16,12 @@
 
 package ethereum
 
-var batchPinMethodABI = ABIElementMarshaling{
+import "github.com/hyperledger/firefly-signer/pkg/abi"
+
+var batchPinMethodABI = &abi.Entry{
 	Name: "pinBatch",
 	Type: "function",
-	Inputs: []ABIArgumentMarshaling{
+	Inputs: abi.ParameterArray{
 		{
 			InternalType: "string",
 			Name:         "namespace",
@@ -48,10 +50,10 @@ var batchPinMethodABI = ABIElementMarshaling{
 	},
 }
 
-var batchPinEventABI = ABIElementMarshaling{
+var batchPinEventABI = &abi.Entry{
 	Name: "BatchPin",
 	Type: "event",
-	Inputs: []ABIArgumentMarshaling{
+	Inputs: abi.ParameterArray{
 		{
 			Indexed:      false,
 			InternalType: "address",
@@ -97,12 +99,12 @@ var batchPinEventABI = ABIElementMarshaling{
 	},
 }
 
-var networkVersionMethodABI = ABIElementMarshaling{
+var networkVersionMethodABI = &abi.Entry{
 	Name:            "networkVersion",
 	Type:            "function",
 	StateMutability: "pure",
-	Inputs:          []ABIArgumentMarshaling{},
-	Outputs: []ABIArgumentMarshaling{
+	Inputs:          abi.ParameterArray{},
+	Outputs: abi.ParameterArray{
 		{
 			InternalType: "uint8",
 			Type:         "uint8",
diff --git a/internal/blockchain/ethereum/ethereum.go b/internal/blockchain/ethereum/ethereum.go
index 7a4627b5b..4c6a57e9f 100644
--- a/internal/blockchain/ethereum/ethereum.go
+++ b/internal/blockchain/ethereum/ethereum.go
@@ -33,6 +33,7 @@ import (
 	"github.com/hyperledger/firefly-common/pkg/i18n"
 	"github.com/hyperledger/firefly-common/pkg/log"
 	"github.com/hyperledger/firefly-common/pkg/wsclient"
+	"github.com/hyperledger/firefly-signer/pkg/abi"
 	"github.com/hyperledger/firefly/internal/coremsgs"
 	"github.com/hyperledger/firefly/internal/metrics"
 	"github.com/hyperledger/firefly/pkg/blockchain"
@@ -42,8 +43,11 @@ import (
 
 const (
 	broadcastBatchEventSignature = "BatchPin(address,uint256,string,bytes32,bytes32,string,bytes32[])"
+	addressType                  = "address"
+	boolType                     = "bool"
 	booleanType                  = "boolean"
 	integerType                  = "integer"
+	tupleType                    = "tuple"
 	stringType                   = "string"
 	arrayType                    = "array"
 	objectType                   = "object"
@@ -105,10 +109,16 @@ type paramDetails struct {
 }
 
 type Schema struct {
-	Type       string             `json:"type"`
-	Details    *paramDetails      `json:"details,omitempty"`
-	Properties map[string]*Schema `json:"properties,omitempty"`
-	Items      *Schema            `json:"items,omitempty"`
+	OneOf       []SchemaType       `json:"oneOf,omitempty"`
+	Type        string             `json:"type,omitempty"`
+	Details     *paramDetails      `json:"details,omitempty"`
+	Properties  map[string]*Schema `json:"properties,omitempty"`
+	Items       *Schema            `json:"items,omitempty"`
+	Description string             `json:"description,omitempty"`
+}
+
+type SchemaType struct {
+	Type string `json:"type"`
 }
 
 func (s *Schema) ToJSON() string {
@@ -116,25 +126,12 @@ func (s *Schema) ToJSON() string {
 	return string(b)
 }
 
-// ABIArgumentMarshaling is abi.ArgumentMarshaling
-type ABIArgumentMarshaling struct {
-	Name         string                  `json:"name"`
-	Type         string                  `json:"type"`
-	InternalType string                  `json:"internalType,omitempty"`
-	Components   []ABIArgumentMarshaling `json:"components,omitempty"`
-	Indexed      bool                    `json:"indexed,omitempty"`
-}
-
-// ABIElementMarshaling is the serialized representation of a method or event in an ABI
-type ABIElementMarshaling struct {
-	Type            string                  `json:"type,omitempty"`
-	Name            string                  `json:"name,omitempty"`
-	Payable         bool                    `json:"payable,omitempty"`
-	Constant        bool                    `json:"constant,omitempty"`
-	Anonymous       bool                    `json:"anonymous,omitempty"`
-	StateMutability string                  `json:"stateMutability,omitempty"`
-	Inputs          []ABIArgumentMarshaling `json:"inputs"`
-	Outputs         []ABIArgumentMarshaling `json:"outputs"`
+type EthconnectMessageRequest struct {
+	Headers EthconnectMessageHeaders `json:"headers,omitempty"`
+	To      string                   `json:"to"`
+	From    string                   `json:"from,omitempty"`
+	Method  *abi.Entry               `json:"method"`
+	Params  []interface{}            `json:"params"`
 }
 
 type EthconnectMessageHeaders struct {
@@ -143,7 +140,7 @@ type EthconnectMessageHeaders struct {
 }
 
 type FFIGenerationInput struct {
-	ABI []ABIElementMarshaling `json:"abi,omitempty"`
+	ABI *abi.ABI `json:"abi,omitempty"`
 }
 
 var addressVerify = regexp.MustCompile("^[0-9a-f]{40}$")
@@ -603,7 +600,7 @@ func wrapError(ctx context.Context, errRes *ethError, res *resty.Response, err e
 	return ffresty.WrapRestErr(ctx, res, err, coremsgs.MsgEthconnectRESTErr)
 }
 
-func (e *Ethereum) buildEthconnectRequestBody(ctx context.Context, messageType, address, signingKey string, abi ABIElementMarshaling, requestID string, input []interface{}, options map[string]interface{}) (map[string]interface{}, error) {
+func (e *Ethereum) buildEthconnectRequestBody(ctx context.Context, messageType, address, signingKey string, abi *abi.Entry, requestID string, input []interface{}, options map[string]interface{}) (map[string]interface{}, error) {
 	headers := EthconnectMessageHeaders{
 		Type: messageType,
 	}
@@ -630,7 +627,7 @@ func (e *Ethereum) buildEthconnectRequestBody(ctx context.Context, messageType,
 	return body, nil
 }
 
-func (e *Ethereum) invokeContractMethod(ctx context.Context, address, signingKey string, abi ABIElementMarshaling, requestID string, input []interface{}, options map[string]interface{}) error {
+func (e *Ethereum) invokeContractMethod(ctx context.Context, address, signingKey string, abi *abi.Entry, requestID string, input []interface{}, options map[string]interface{}) error {
 	if e.metrics.IsMetricsEnabled() {
 		e.metrics.BlockchainTransaction(address, abi.Name)
 	}
@@ -655,7 +652,7 @@ func (e *Ethereum) invokeContractMethod(ctx context.Context, address, signingKey
 	return nil
 }
 
-func (e *Ethereum) queryContractMethod(ctx context.Context, address string, abi ABIElementMarshaling, input []interface{}, options map[string]interface{}) (*resty.Response, error) {
+func (e *Ethereum) queryContractMethod(ctx context.Context, address string, abi *abi.Entry, input []interface{}, options map[string]interface{}) (*resty.Response, error) {
 	if e.metrics.IsMetricsEnabled() {
 		e.metrics.BlockchainQuery(address, abi.Name)
 	}
@@ -797,38 +794,49 @@ func (e *Ethereum) GetFFIParamValidator(ctx context.Context) (core.FFIParamValid
 	return &FFIParamValidator{}, nil
 }
 
-func (e *Ethereum) FFIEventDefinitionToABI(ctx context.Context, event *core.FFIEventDefinition) (ABIElementMarshaling, error) {
-	abiElement := ABIElementMarshaling{
+func (e *Ethereum) FFIEventDefinitionToABI(ctx context.Context, event *core.FFIEventDefinition) (*abi.Entry, error) {
+	abiInputs, err := e.convertFFIParamsToABIParameters(ctx, event.Params)
+	if err != nil {
+		return nil, err
+	}
+	abiEntry := &abi.Entry{
 		Name:   event.Name,
 		Type:   "event",
-		Inputs: make([]ABIArgumentMarshaling, len(event.Params)),
+		Inputs: abiInputs,
 	}
-
-	if err := e.addParamsToList(ctx, abiElement.Inputs, event.Params); err != nil {
-		return abiElement, err
+	if event.Details != nil {
+		abiEntry.Anonymous = event.Details.GetBool("anonymous")
 	}
-	return abiElement, nil
+	return abiEntry, nil
 }
 
-func (e *Ethereum) FFIMethodToABI(ctx context.Context, method *core.FFIMethod) (ABIElementMarshaling, error) {
-	abiElement := ABIElementMarshaling{
-		Name:    method.Name,
-		Type:    "function",
-		Inputs:  make([]ABIArgumentMarshaling, len(method.Params)),
-		Outputs: make([]ABIArgumentMarshaling, len(method.Returns)),
+func (e *Ethereum) FFIMethodToABI(ctx context.Context, method *core.FFIMethod, input map[string]interface{}) (*abi.Entry, error) {
+	abiInputs, err := e.convertFFIParamsToABIParameters(ctx, method.Params)
+	if err != nil {
+		return nil, err
 	}
 
-	if err := e.addParamsToList(ctx, abiElement.Inputs, method.Params); err != nil {
-		return abiElement, err
+	abiOutputs, err := e.convertFFIParamsToABIParameters(ctx, method.Returns)
+	if err != nil {
+		return nil, err
 	}
-	if err := e.addParamsToList(ctx, abiElement.Outputs, method.Returns); err != nil {
-		return abiElement, err
+	abiEntry := &abi.Entry{
+		Name:    method.Name,
+		Type:    "function",
+		Inputs:  abiInputs,
+		Outputs: abiOutputs,
 	}
-
-	return abiElement, nil
+	if method.Details != nil {
+		if stateMutability, ok := method.Details.GetStringOk("stateMutability"); ok {
+			abiEntry.StateMutability = abi.StateMutability(stateMutability)
+		}
+		abiEntry.Payable = method.Details.GetBool("payable")
+		abiEntry.Constant = method.Details.GetBool("constant")
+	}
+	return abiEntry, nil
 }
 
-func ABIArgumentToTypeString(typeName string, components []ABIArgumentMarshaling) string {
+func ABIArgumentToTypeString(typeName string, components abi.ParameterArray) string {
 	if strings.HasPrefix(typeName, "tuple") {
 		suffix := typeName[5:]
 		children := make([]string, len(components))
@@ -840,7 +848,7 @@ func ABIArgumentToTypeString(typeName string, components []ABIArgumentMarshaling
 	return typeName
 }
 
-func ABIMethodToSignature(abi *ABIElementMarshaling) string {
+func ABIMethodToSignature(abi *abi.Entry) string {
 	result := abi.Name + "("
 	if len(abi.Inputs) > 0 {
 		types := make([]string, len(abi.Inputs))
@@ -858,49 +866,55 @@ func (e *Ethereum) GenerateEventSignature(ctx context.Context, event *core.FFIEv
 	if err != nil {
 		return ""
 	}
-	return ABIMethodToSignature(&abi)
+	return ABIMethodToSignature(abi)
 }
 
-func (e *Ethereum) addParamsToList(ctx context.Context, abiParamList []ABIArgumentMarshaling, params core.FFIParams) error {
+func (e *Ethereum) convertFFIParamsToABIParameters(ctx context.Context, params core.FFIParams) (abi.ParameterArray, error) {
+	abiParamList := make(abi.ParameterArray, len(params))
 	for i, param := range params {
 		c := core.NewFFISchemaCompiler()
 		v, _ := e.GetFFIParamValidator(ctx)
 		c.RegisterExtension(v.GetExtensionName(), v.GetMetaSchema(), v)
 		err := c.AddResource(param.Name, strings.NewReader(param.Schema.String()))
 		if err != nil {
-			return err
+			return nil, err
 		}
 		s, err := c.Compile(param.Name)
 		if err != nil {
-			return err
+			return nil, err
 		}
 		abiParamList[i] = processField(param.Name, s)
 	}
-	return nil
+	return abiParamList, nil
 }
 
-func processField(name string, schema *jsonschema.Schema) ABIArgumentMarshaling {
+func processField(name string, schema *jsonschema.Schema) *abi.Parameter {
 	details := getParamDetails(schema)
-	arg := ABIArgumentMarshaling{
+	parameter := &abi.Parameter{
 		Name:         name,
 		Type:         details.Type,
 		InternalType: details.InternalType,
 		Indexed:      details.Indexed,
 	}
-	if schema.Types[0] == objectType {
-		arg.Components = buildABIArgumentArray(schema.Properties)
+	if len(schema.Types) > 0 {
+		switch schema.Types[0] {
+		case objectType:
+			parameter.Components = buildABIParameterArrayForObject(schema.Properties)
+		case arrayType:
+			parameter.Components = buildABIParameterArrayForObject(schema.Items2020.Properties)
+		}
 	}
-	return arg
+	return parameter
 }
 
-func buildABIArgumentArray(properties map[string]*jsonschema.Schema) []ABIArgumentMarshaling {
-	args := make([]ABIArgumentMarshaling, len(properties))
+func buildABIParameterArrayForObject(properties map[string]*jsonschema.Schema) abi.ParameterArray {
+	parameters := make(abi.ParameterArray, len(properties))
 	for propertyName, propertySchema := range properties {
 		details := getParamDetails(propertySchema)
-		arg := processField(propertyName, propertySchema)
-		args[*details.Index] = arg
+		parameter := processField(propertyName, propertySchema)
+		parameters[*details.Index] = parameter
 	}
-	return args
+	return parameters
 }
 
 func getParamDetails(schema *jsonschema.Schema) *paramDetails {
@@ -924,13 +938,14 @@ func getParamDetails(schema *jsonschema.Schema) *paramDetails {
 	return paramDetails
 }
 
-func (e *Ethereum) prepareRequest(ctx context.Context, method *core.FFIMethod, input map[string]interface{}) (ABIElementMarshaling, []interface{}, error) {
+func (e *Ethereum) prepareRequest(ctx context.Context, method *core.FFIMethod, input map[string]interface{}) (*abi.Entry, []interface{}, error) {
 	orderedInput := make([]interface{}, len(method.Params))
-	abi, err := e.FFIMethodToABI(ctx, method)
+	abi, err := e.FFIMethodToABI(ctx, method, input)
 	if err != nil {
 		return abi, orderedInput, err
 	}
 	for i, ffiParam := range method.Params {
+
 		orderedInput[i] = input[ffiParam.Name]
 	}
 	return abi, orderedInput, nil
@@ -956,121 +971,167 @@ func (e *Ethereum) GenerateFFI(ctx context.Context, generationRequest *core.FFIG
 	if err != nil {
 		return nil, i18n.NewError(ctx, coremsgs.MsgFFIGenerationFailed, "unable to deserialize JSON as ABI")
 	}
-	if len(input.ABI) == 0 {
+	if len(*input.ABI) == 0 {
 		return nil, i18n.NewError(ctx, coremsgs.MsgFFIGenerationFailed, "ABI is empty")
 	}
-	ffi := e.convertABIToFFI(generationRequest.Namespace, generationRequest.Name, generationRequest.Version, generationRequest.Description, input.ABI)
-	return ffi, nil
+	return e.convertABIToFFI(ctx, generationRequest.Namespace, generationRequest.Name, generationRequest.Version, generationRequest.Description, input.ABI)
 }
 
-func (e *Ethereum) convertABIToFFI(ns, name, version, description string, abi []ABIElementMarshaling) *core.FFI {
+func (e *Ethereum) convertABIToFFI(ctx context.Context, ns, name, version, description string, abi *abi.ABI) (*core.FFI, error) {
 	ffi := &core.FFI{
 		Namespace:   ns,
 		Name:        name,
 		Version:     version,
 		Description: description,
-		Methods:     []*core.FFIMethod{},
-		Events:      []*core.FFIEvent{},
-	}
-
-	for _, element := range abi {
-		switch element.Type {
-		case "event":
-			event := &core.FFIEvent{
-				FFIEventDefinition: core.FFIEventDefinition{
-					Name:   element.Name,
-					Params: e.convertABIArgumentsToFFI(element.Inputs),
-				},
-			}
-			ffi.Events = append(ffi.Events, event)
-		case "function":
-			method := &core.FFIMethod{
-				Name:    element.Name,
-				Params:  e.convertABIArgumentsToFFI(element.Inputs),
-				Returns: e.convertABIArgumentsToFFI(element.Outputs),
-			}
-			ffi.Methods = append(ffi.Methods, method)
+		Methods:     make([]*core.FFIMethod, len(abi.Functions())),
+		Events:      make([]*core.FFIEvent, len(abi.Events())),
+	}
+	i := 0
+	for _, f := range abi.Functions() {
+		method, err := e.convertABIFunctionToFFIMethod(ctx, f)
+		if err != nil {
+			return nil, err
+		}
+		ffi.Methods[i] = method
+		i++
+	}
+	i = 0
+	for _, f := range abi.Events() {
+		event, err := e.convertABIEventToFFIEvent(ctx, f)
+		if err != nil {
+			return nil, err
 		}
+		ffi.Events[i] = event
+		i++
 	}
-	return ffi
+	return ffi, nil
 }
 
-func (e *Ethereum) convertABIArgumentsToFFI(args []ABIArgumentMarshaling) core.FFIParams {
-	ffiParams := core.FFIParams{}
-	for _, arg := range args {
+func (e *Ethereum) convertABIFunctionToFFIMethod(ctx context.Context, abiFunction *abi.Entry) (*core.FFIMethod, error) {
+	params := make([]*core.FFIParam, len(abiFunction.Inputs))
+	returns := make([]*core.FFIParam, len(abiFunction.Outputs))
+	details := map[string]interface{}{}
+	for i, input := range abiFunction.Inputs {
+		typeComponent, err := input.TypeComponentTreeCtx(ctx)
+		if err != nil {
+			return nil, err
+		}
+		schema := e.getSchemaForABIInput(ctx, typeComponent)
 		param := &core.FFIParam{
-			Name: arg.Name,
+			Name:   input.Name,
+			Schema: fftypes.JSONAnyPtr(schema.ToJSON()),
 		}
-		s := e.getSchema(arg)
-		param.Schema = fftypes.JSONAnyPtr(s.ToJSON())
-		ffiParams = append(ffiParams, param)
+		params[i] = param
 	}
-	return ffiParams
-}
-
-func (e *Ethereum) getSchema(arg ABIArgumentMarshaling) *Schema {
-	s := &Schema{
-		Type: e.getFFIType(arg.Type),
-		Details: &paramDetails{
-			Type:         arg.Type,
-			InternalType: arg.InternalType,
-			Indexed:      arg.Indexed,
-		},
+	for i, output := range abiFunction.Outputs {
+		typeComponent, err := output.TypeComponentTreeCtx(ctx)
+		if err != nil {
+			return nil, err
+		}
+		schema := e.getSchemaForABIInput(ctx, typeComponent)
+		param := &core.FFIParam{
+			Name:   output.Name,
+			Schema: fftypes.JSONAnyPtr(schema.ToJSON()),
+		}
+		returns[i] = param
 	}
-	var properties map[string]*Schema
-	if len(arg.Components) > 0 {
-		properties = e.getSchemaForObjectComponents(arg)
+	if abiFunction.StateMutability != "" {
+		details["stateMutability"] = string(abiFunction.StateMutability)
 	}
-	if s.Type == arrayType {
-		levels := strings.Count(arg.Type, "[]")
-		innerType := e.getFFIType(strings.ReplaceAll(arg.Type, "[]", ""))
-		innerSchema := &Schema{
-			Type: innerType,
-		}
-		if len(arg.Components) > 0 {
-			innerSchema.Properties = e.getSchemaForObjectComponents(arg)
+	if abiFunction.Payable {
+		details["payable"] = true
+	}
+	if abiFunction.Constant {
+		details["constant"] = true
+	}
+	return &core.FFIMethod{
+		Name:    abiFunction.Name,
+		Params:  params,
+		Returns: returns,
+		Details: details,
+	}, nil
+}
+
+func (e *Ethereum) convertABIEventToFFIEvent(ctx context.Context, abiEvent *abi.Entry) (*core.FFIEvent, error) {
+	params := make([]*core.FFIParam, len(abiEvent.Inputs))
+	details := map[string]interface{}{}
+	for i, output := range abiEvent.Inputs {
+		typeComponent, err := output.TypeComponentTreeCtx(ctx)
+		if err != nil {
+			return nil, err
 		}
-		for i := 1; i < levels; i++ {
-			innerSchema = &Schema{
-				Type:  arrayType,
-				Items: innerSchema,
-			}
+		schema := e.getSchemaForABIInput(ctx, typeComponent)
+		param := &core.FFIParam{
+			Name:   output.Name,
+			Schema: fftypes.JSONAnyPtr(schema.ToJSON()),
 		}
-		s.Items = innerSchema
-	} else {
-		s.Properties = properties
+		params[i] = param
+	}
+	if abiEvent.Anonymous {
+		details["anonymous"] = true
 	}
-	return s
+	return &core.FFIEvent{
+		FFIEventDefinition: core.FFIEventDefinition{
+			Name:    abiEvent.Name,
+			Params:  params,
+			Details: details,
+		},
+	}, nil
 }
 
-func (e *Ethereum) getSchemaForObjectComponents(arg ABIArgumentMarshaling) map[string]*Schema {
-	m := make(map[string]*Schema, len(arg.Components))
-	for i, component := range arg.Components {
-		componentSchema := e.getSchema(component)
-		componentSchema.Details.Index = new(int)
-		*componentSchema.Details.Index = i
-		m[component.Name] = componentSchema
+func (e *Ethereum) getSchemaForABIInput(ctx context.Context, typeComponent abi.TypeComponent) *Schema {
+	schema := &Schema{
+		Details: &paramDetails{
+			Type:         typeComponent.Parameter().Type,
+			InternalType: typeComponent.Parameter().InternalType,
+			Indexed:      typeComponent.Parameter().Indexed,
+		},
 	}
-	return m
+	switch typeComponent.ComponentType() {
+	case abi.ElementaryComponent:
+		t := e.getFFIType(typeComponent.ElementaryType().String())
+		if t == core.FFIInputTypeInteger {
+			schema.OneOf = []SchemaType{
+				{Type: "string"},
+				{Type: "integer"},
+			}
+			schema.Description = i18n.Expand(ctx, coremsgs.APIIntegerDescription)
+		} else {
+			schema.Type = t.String()
+		}
+	case abi.FixedArrayComponent, abi.DynamicArrayComponent:
+		schema.Type = arrayType
+		childSchema := e.getSchemaForABIInput(ctx, typeComponent.ArrayChild())
+		schema.Items = childSchema
+		schema.Details = childSchema.Details
+		childSchema.Details = nil
+	case abi.TupleComponent:
+		schema.Type = objectType
+		schema.Properties = make(map[string]*Schema, len(typeComponent.TupleChildren()))
+		for i, tupleChild := range typeComponent.TupleChildren() {
+			childSchema := e.getSchemaForABIInput(ctx, tupleChild)
+			childSchema.Details.Index = new(int)
+			*childSchema.Details.Index = i
+			schema.Properties[tupleChild.KeyName()] = childSchema
+		}
+	}
+	return schema
 }
 
-func (e *Ethereum) getFFIType(solitidyType string) string {
-
-	switch solitidyType {
+func (e *Ethereum) getFFIType(solidityType string) core.FFIInputType {
+	switch solidityType {
 	case stringType, "address":
-		return stringType
-	case "bool":
-		return booleanType
-	case "tuple":
-		return objectType
+		return core.FFIInputTypeString
+	case boolType:
+		return core.FFIInputTypeBoolean
+	case tupleType:
+		return core.FFIInputTypeObject
 	default:
 		switch {
-		case strings.HasSuffix(solitidyType, "[]"):
-			return arrayType
-		case strings.Contains(solitidyType, "byte"):
-			return stringType
-		case strings.Contains(solitidyType, "int"):
-			return integerType
+		case strings.Contains(solidityType, "byte"):
+			return core.FFIInputTypeString
+		case strings.Contains(solidityType, "int"):
+			return core.FFIInputTypeInteger
 		}
 	}
 	return ""
diff --git a/internal/blockchain/ethereum/eventstream.go b/internal/blockchain/ethereum/eventstream.go
index 4059a60a4..459184639 100644
--- a/internal/blockchain/ethereum/eventstream.go
+++ b/internal/blockchain/ethereum/eventstream.go
@@ -25,6 +25,7 @@ import (
 	"github.com/go-resty/resty/v2"
 	"github.com/hyperledger/firefly-common/pkg/ffresty"
 	"github.com/hyperledger/firefly-common/pkg/log"
+	"github.com/hyperledger/firefly-signer/pkg/abi"
 	"github.com/hyperledger/firefly/internal/coremsgs"
 	"github.com/hyperledger/firefly/pkg/core"
 )
@@ -45,12 +46,12 @@ type eventStream struct {
 }
 
 type subscription struct {
-	ID        string               `json:"id"`
-	Name      string               `json:"name,omitempty"`
-	Stream    string               `json:"stream"`
-	FromBlock string               `json:"fromBlock"`
-	Address   string               `json:"address"`
-	Event     ABIElementMarshaling `json:"event"`
+	ID        string     `json:"id"`
+	Name      string     `json:"name,omitempty"`
+	Stream    string     `json:"stream"`
+	FromBlock string     `json:"fromBlock"`
+	Address   string     `json:"address"`
+	Event     *abi.Entry `json:"event"`
 }
 
 func (s *streamManager) getEventStreams(ctx context.Context) (streams []*eventStream, err error) {
@@ -134,7 +135,7 @@ func (s *streamManager) getSubscriptions(ctx context.Context) (subs []*subscript
 	return subs, nil
 }
 
-func (s *streamManager) createSubscription(ctx context.Context, location *Location, stream, subName, fromBlock string, abi ABIElementMarshaling) (*subscription, error) {
+func (s *streamManager) createSubscription(ctx context.Context, location *Location, stream, subName, fromBlock string, abi *abi.Entry) (*subscription, error) {
 	// Map FireFly "firstEvent" values to Ethereum "fromBlock" values
 	switch fromBlock {
 	case string(core.SubOptsFirstEventOldest):
@@ -170,7 +171,7 @@ func (s *streamManager) deleteSubscription(ctx context.Context, subID string) er
 	return nil
 }
 
-func (s *streamManager) ensureFireFlySubscription(ctx context.Context, instancePath, fromBlock, stream string, abi ABIElementMarshaling) (sub *subscription, err error) {
+func (s *streamManager) ensureFireFlySubscription(ctx context.Context, instancePath, fromBlock, stream string, abi *abi.Entry) (sub *subscription, err error) {
 	// Include a hash of the instance path in the subscription, so if we ever point at a different
 	// contract configuration, we re-subscribe from block 0.
 	// We don't need full strength hashing, so just use the first 16 chars for readability.
diff --git a/internal/blockchain/ethereum/ffi_param_validator.go b/internal/blockchain/ethereum/ffi_param_validator.go
index f64b1001f..898b94e2a 100644
--- a/internal/blockchain/ethereum/ffi_param_validator.go
+++ b/internal/blockchain/ethereum/ffi_param_validator.go
@@ -33,31 +33,40 @@ var bytesRegex, _ = regexp.Compile("^bytes([0-9]{1,2})?")
 func (v *FFIParamValidator) Compile(ctx jsonschema.CompilerContext, m map[string]interface{}) (jsonschema.ExtSchema, error) {
 	valid := true
 	if details, ok := m["details"]; ok {
+		var jsonTypeString string
 		n, _ := details.(map[string]interface{})
 		blockchainType := n["type"].(string)
-		jsonType := m["type"].(string)
-		switch jsonType {
-		case "string":
-			if blockchainType != "string" &&
-				blockchainType != "address" &&
+		jsonType, ok := m["type"]
+		if ok {
+			jsonTypeString = jsonType.(string)
+		} else {
+			_, ok := m["oneOf"]
+			if ok {
+				jsonTypeString = integerType
+			}
+		}
+		switch jsonTypeString {
+		case stringType:
+			if blockchainType != stringType &&
+				blockchainType != addressType &&
 				!isEthereumNumberType(blockchainType) &&
 				!isEthereumBytesType(blockchainType) {
 				valid = false
 			}
-		case "integer":
+		case integerType:
 			if !isEthereumNumberType(blockchainType) {
 				valid = false
 			}
-		case "boolean":
-			if blockchainType != "bool" {
+		case booleanType:
+			if blockchainType != boolType {
 				valid = false
 			}
-		case "array":
+		case arrayType:
 			if !strings.HasSuffix(blockchainType, "[]") {
 				valid = false
 			}
-		case "object":
-			if blockchainType != "tuple" {
+		case objectType:
+			if blockchainType != tupleType {
 				valid = false
 			}
 		}
@@ -75,118 +84,129 @@ func (v *FFIParamValidator) GetMetaSchema() *jsonschema.Schema {
 		"$ref": "#/$defs/ethereumParam",
 		"$defs": {
 			"ethereumParam": {
-			"oneOf": [
-				{
-				"type": "object",
-				"properties": {
-					"type": {
-					"type": "string",
-					"not": {
-						"const": "object"
+				"oneOf": [
+					{
+						"type": "object",
+						"properties": {
+							"type": {
+								"type": "string",
+								"not": {
+									"const": "object"
+								}
+							},
+							"details": {
+								"$ref": "#/$defs/details"
+							}
+						},
+						"required": [
+							"details"
+						]
+					},
+					{
+						"type": "object",
+						"properties": {
+							"type": {
+								"const": "object"
+							},
+							"details": {
+								"$ref": "#/$defs/details"
+							},
+							"properties": {
+								"type": "object",
+								"patternProperties": {
+									".*": {
+										"$ref": "#/$defs/ethereumObjectChildParam"
+									}
+								}
+							}
+						},
+						"required": [
+							"details",
+							"type"
+						]
 					}
+				]
+			},
+			"ethereumObjectChildParam": {
+				"oneOf": [
+					{
+						"type": "object",
+						"properties": {
+							"type": {
+								"type": "string",
+								"not": {
+									"const": "object"
+								}
+							},
+							"details": {
+								"$ref": "#/$defs/objectFieldDetails"
+							}
+						},
+						"required": [
+							"details"
+						]
 					},
-					"details": {
-					"$ref": "#/$defs/details"
+					{
+						"type": "object",
+						"properties": {
+							"type": {
+								"const": "object"
+							},
+							"details": {
+								"$ref": "#/$defs/objectFieldDetails"
+							},
+							"properties": {
+								"type": "object",
+								"patternProperties": {
+									".*": {
+										"$ref": "#/$defs/ethereumObjectChildParam"
+									}
+								}
+							}
+						},
+						"required": [
+							"details"
+						]
 					}
-				},
-				"required": ["details"]
-				},
-				{
+				]
+			},
+			"details": {
 				"type": "object",
 				"properties": {
 					"type": {
-					"const": "object"
+						"type": "string"
 					},
-					"details": {
-					"$ref": "#/$defs/details"
+					"internalType": {
+						"type": "string"
 					},
-					"properties": {
-					"type": "object",
-					"patternProperties": {
-						".*": {
-						"$ref": "#/$defs/ethereumObjectChildParam"
-						}
-					}
+					"indexed": {
+						"type": "boolean"
 					}
 				},
-				"required": ["details"]
-				}
-			]
+				"required": [
+					"type"
+				]
 			},
-
-			"ethereumObjectChildParam": {
-			"oneOf": [
-				{
+			"objectFieldDetails": {
 				"type": "object",
 				"properties": {
 					"type": {
-					"type": "string",
-					"not": {
-						"const": "object"
-					}
+						"type": "string"
 					},
-					"details": {
-					"$ref": "#/$defs/objectFieldDetails"
-					}
-				},
-				"required": ["details"]
-				},
-				{
-				"type": "object",
-				"properties": {
-					"type": {
-					"const": "object"
+					"internalType": {
+						"type": "string"
 					},
-					"details": {
-					"$ref": "#/$defs/objectFieldDetails"
+					"indexed": {
+						"type": "boolean"
 					},
-					"properties": {
-					"type": "object",
-					"patternProperties": {
-						".*": {
-						"$ref": "#/$defs/ethereumObjectChildParam"
-						}
-					}
+					"index": {
+						"type": "integer"
 					}
 				},
-				"required": ["details"]
-				}
-			]
-			},
-
-			"details": {
-			"type": "object",
-			"properties": {
-				"type": {
-				"type": "string"
-				},
-				"internalType": {
-				"type": "string"
-				},
-				"indexed": {
-				"type": "boolean"
-				}
-			},
-			"required": ["type"]
-			},
-
-			"objectFieldDetails": {
-			"type": "object",
-			"properties": {
-				"type": {
-				"type": "string"
-				},
-				"internalType": {
-				"type": "string"
-				},
-				"indexed": {
-				"type": "boolean"
-				},
-				"index": {
-				"type": "integer"
-				}
-			},
-			"required": ["type", "index"]
+				"required": [
+					"type",
+					"index"
+				]
 			}
 		}
 	}`)
diff --git a/internal/coremsgs/en_api_translations.go b/internal/coremsgs/en_api_translations.go
index d5f4f9eaf..df32b0580 100644
--- a/internal/coremsgs/en_api_translations.go
+++ b/internal/coremsgs/en_api_translations.go
@@ -189,4 +189,9 @@ var (
 	APIHistogramStartTimeParam = ffm("api.histogramStartTime", "Start time of the data to be fetched")
 	APIHistogramEndTimeParam   = ffm("api.histogramEndTime", "End time of the data to be fetched")
 	APIHistogramBucketsParam   = ffm("api.histogramBuckets", "Number of buckets between start time and end time")
+	APIIntegerDescription      = ffm("api.integer", "An integer. You are recommended to use a JSON string. A JSON number can be used for values up to the safe maximum.")
+
+	APISmartContractDetails      = ffm("api.smartContractDetails", "Additional smart contract details")
+	APISmartContractDetailsKey   = ffm("api.smartContractDetailsKey", "Key")
+	APISmartContractDetailsValue = ffm("api.smartContractDetailsValue", "Value")
 )
diff --git a/internal/coremsgs/en_struct_descriptions.go b/internal/coremsgs/en_struct_descriptions.go
index 5e6fce00d..f884bc79d 100644
--- a/internal/coremsgs/en_struct_descriptions.go
+++ b/internal/coremsgs/en_struct_descriptions.go
@@ -100,7 +100,7 @@ var (
 
 	// Member field descriptions
 	MemberIdentity = ffm("Member.identity", "The DID of the group member")
-	MembertNode    = ffm("Member.node", "The UUID of the node that receives a copy of the off-chain message for the identity")
+	MemberNode     = ffm("Member.node", "The UUID of the node that receives a copy of the off-chain message for the identity")
 
 	// DataRef field descriptions
 	DataRefID   = ffm("DataRef.id", "The UUID of the referenced data resource")
@@ -247,6 +247,7 @@ var (
 	FFIMethodDescription = ffm("FFIMethod.description", "A description of the smart contract method")
 	FFIMethodParams      = ffm("FFIMethod.params", "An array of method parameter/argument definitions")
 	FFIMethodReturns     = ffm("FFIMethod.returns", "An array of method return definitions")
+	FFIMethodDetails     = ffm("FFIMethod.details", "Additional blockchain specific fields about this method from the original smart contract. Used by the blockchain plugin and for documentation generation.")
 
 	// FFIEvent field descriptions
 	FFIEventID          = ffm("FFIEvent.id", "The UUID of the FFI event definition")
@@ -257,6 +258,7 @@ var (
 	FFIEventDescription = ffm("FFIEvent.description", "A description of the smart contract event")
 	FFIEventParams      = ffm("FFIEvent.params", "An array of event parameter/argument definitions")
 	FFIEventSignature   = ffm("FFIEvent.signature", "The stringified signature of the event, as computed by the blockchain plugin")
+	FFIEventDetails     = ffm("FFIEvent.details", "Additional blockchain specific fields about this event from the original smart contract. Used by the blockchain plugin and for documentation generation.")
 
 	// FFIParam field descriptions
 	FFIParamName   = ffm("FFIParam.name", "The name of the parameter. Note that parameters must be ordered correctly on the FFI, according to the order in the blockchain smart contract")
diff --git a/internal/database/sqlcommon/ffi_events_sql.go b/internal/database/sqlcommon/ffi_events_sql.go
index 83b7fdca4..f3baa90f6 100644
--- a/internal/database/sqlcommon/ffi_events_sql.go
+++ b/internal/database/sqlcommon/ffi_events_sql.go
@@ -38,6 +38,7 @@ var (
 		"pathname",
 		"description",
 		"params",
+		"details",
 	}
 	ffiEventFilterFieldMap = map[string]string{
 		"interface": "interface_id",
@@ -87,6 +88,7 @@ func (s *SQLCommon) UpsertFFIEvent(ctx context.Context, event *core.FFIEvent) (e
 					event.Pathname,
 					event.Description,
 					event.Params,
+					event.Details,
 				),
 			func() {
 				s.callbacks.UUIDCollectionNSEvent(database.CollectionFFIEvents, core.ChangeEventTypeCreated, event.Namespace, event.ID)
@@ -109,6 +111,7 @@ func (s *SQLCommon) ffiEventResult(ctx context.Context, row *sql.Rows) (*core.FF
 		&event.Pathname,
 		&event.Description,
 		&event.Params,
+		&event.Details,
 	)
 	if err != nil {
 		return nil, i18n.WrapError(ctx, err, coremsgs.MsgDBReadErr, ffieventsTable)
diff --git a/internal/database/sqlcommon/ffi_methods_sql.go b/internal/database/sqlcommon/ffi_methods_sql.go
index 219251695..7363a61a5 100644
--- a/internal/database/sqlcommon/ffi_methods_sql.go
+++ b/internal/database/sqlcommon/ffi_methods_sql.go
@@ -39,6 +39,7 @@ var (
 		"description",
 		"params",
 		"returns",
+		"details",
 	}
 	ffiMethodFilterFieldMap = map[string]string{
 		"interface": "interface_id",
@@ -90,6 +91,7 @@ func (s *SQLCommon) UpsertFFIMethod(ctx context.Context, method *core.FFIMethod)
 					method.Description,
 					method.Params,
 					method.Returns,
+					method.Details,
 				),
 			func() {
 				s.callbacks.UUIDCollectionNSEvent(database.CollectionFFIMethods, core.ChangeEventTypeCreated, method.Namespace, method.ID)
@@ -113,6 +115,7 @@ func (s *SQLCommon) ffiMethodResult(ctx context.Context, row *sql.Rows) (*core.F
 		&method.Description,
 		&method.Params,
 		&method.Returns,
+		&method.Details,
 	)
 	if err != nil {
 		return nil, i18n.WrapError(ctx, err, coremsgs.MsgDBReadErr, ffimethodsTable)
diff --git a/internal/reference/reference.go b/internal/reference/reference.go
index cb5fa4f06..d53cc4a6a 100644
--- a/internal/reference/reference.go
+++ b/internal/reference/reference.go
@@ -184,6 +184,9 @@ func GenerateObjectsReferenceMarkdown(ctx context.Context) (map[string][]byte, e
 							}`),
 						},
 					},
+					Details: fftypes.JSONObject{
+						"stateMutability": "viewable",
+					},
 				},
 				{
 					ID:          fftypes.MustParseUUID("fc6f54ee-2e3c-4e56-b17c-4a1a0ae7394b"),
@@ -204,6 +207,9 @@ func GenerateObjectsReferenceMarkdown(ctx context.Context) (map[string][]byte, e
 						},
 					},
 					Returns: core.FFIParams{},
+					Details: fftypes.JSONObject{
+						"stateMutability": "payable",
+					},
 				},
 			},
 			Events: []*core.FFIEvent{
@@ -237,6 +243,7 @@ func GenerateObjectsReferenceMarkdown(ctx context.Context) (map[string][]byte, e
 								}`),
 							},
 						},
+						Details: fftypes.JSONObject{},
 					},
 				},
 			},
diff --git a/pkg/core/ffi.go b/pkg/core/ffi.go
index e2818f66a..504cacf80 100644
--- a/pkg/core/ffi.go
+++ b/pkg/core/ffi.go
@@ -26,6 +26,22 @@ import (
 	"github.com/santhosh-tekuri/jsonschema/v5"
 )
 
+// FFIInputType is the type of a JSON field in a request to FireFly's API
+type FFIInputType = fftypes.FFEnum
+
+var (
+	// FFIInputTypeInteger is a json integer or string to be treated as an integer
+	FFIInputTypeInteger = fftypes.FFEnumValue("ffiinputtype", "integer")
+	// FFIInputTypeString is a JSON string
+	FFIInputTypeString = fftypes.FFEnumValue("ffiinputtype", "string")
+	// FFIInputTypeArray is a JSON boolean
+	FFIInputTypeBoolean = fftypes.FFEnumValue("ffiinputtype", "boolean")
+	// FFIInputTypeArray is a JSON array
+	FFIInputTypeArray = fftypes.FFEnumValue("ffiinputtype", "array")
+	// FFIInputTypeObject is a JSON object
+	FFIInputTypeObject = fftypes.FFEnumValue("ffiinputtype", "object")
+)
+
 type FFIParamValidator interface {
 	Compile(ctx jsonschema.CompilerContext, m map[string]interface{}) (jsonschema.ExtSchema, error)
 	GetMetaSchema() *jsonschema.Schema
@@ -50,20 +66,22 @@ type FFI struct {
 }
 
 type FFIMethod struct {
-	ID          *fftypes.UUID `ffstruct:"FFIMethod" json:"id,omitempty" ffexcludeinput:"true"`
-	Interface   *fftypes.UUID `ffstruct:"FFIMethod" json:"interface,omitempty" ffexcludeinput:"true"`
-	Name        string        `ffstruct:"FFIMethod" json:"name"`
-	Namespace   string        `ffstruct:"FFIMethod" json:"namespace,omitempty" ffexcludeinput:"true"`
-	Pathname    string        `ffstruct:"FFIMethod" json:"pathname" ffexcludeinput:"true"`
-	Description string        `ffstruct:"FFIMethod" json:"description"`
-	Params      FFIParams     `ffstruct:"FFIMethod" json:"params"`
-	Returns     FFIParams     `ffstruct:"FFIMethod" json:"returns"`
+	ID          *fftypes.UUID      `ffstruct:"FFIMethod" json:"id,omitempty" ffexcludeinput:"true"`
+	Interface   *fftypes.UUID      `ffstruct:"FFIMethod" json:"interface,omitempty" ffexcludeinput:"true"`
+	Name        string             `ffstruct:"FFIMethod" json:"name"`
+	Namespace   string             `ffstruct:"FFIMethod" json:"namespace,omitempty" ffexcludeinput:"true"`
+	Pathname    string             `ffstruct:"FFIMethod" json:"pathname" ffexcludeinput:"true"`
+	Description string             `ffstruct:"FFIMethod" json:"description"`
+	Params      FFIParams          `ffstruct:"FFIMethod" json:"params"`
+	Returns     FFIParams          `ffstruct:"FFIMethod" json:"returns"`
+	Details     fftypes.JSONObject `ffstruct:"FFIMethod" json:"details,omitempty"`
 }
 
 type FFIEventDefinition struct {
-	Name        string    `ffstruct:"FFIEvent" json:"name"`
-	Description string    `ffstruct:"FFIEvent" json:"description"`
-	Params      FFIParams `ffstruct:"FFIEvent" json:"params"`
+	Name        string             `ffstruct:"FFIEvent" json:"name"`
+	Description string             `ffstruct:"FFIEvent" json:"description"`
+	Params      FFIParams          `ffstruct:"FFIEvent" json:"params"`
+	Details     fftypes.JSONObject `ffstruct:"FFIEvent" json:"details,omitempty"`
 }
 
 type FFIEvent struct {
@@ -112,21 +130,21 @@ func (f *FFI) SetBroadcastMessage(msgID *fftypes.UUID) {
 }
 
 // Scan implements sql.Scanner
-func (m *FFIParams) Scan(src interface{}) error {
+func (p *FFIParams) Scan(src interface{}) error {
 	switch src := src.(type) {
 	case nil:
-		m = nil
+		p = nil
 		return nil
 	case string:
-		return json.Unmarshal([]byte(src), &m)
+		return json.Unmarshal([]byte(src), &p)
 	case []byte:
-		return json.Unmarshal(src, &m)
+		return json.Unmarshal(src, &p)
 	default:
-		return i18n.NewError(context.Background(), i18n.MsgTypeRestoreFailed, src, m)
+		return i18n.NewError(context.Background(), i18n.MsgTypeRestoreFailed, src, p)
 	}
 }
 
-func (m FFIParams) Value() (driver.Value, error) {
-	bytes, _ := json.Marshal(m)
+func (p FFIParams) Value() (driver.Value, error) {
+	bytes, _ := json.Marshal(p)
 	return bytes, nil
 }
diff --git a/pkg/core/ffi_param_validator.go b/pkg/core/ffi_param_validator.go
index 81e1ff6c8..2ac925686 100644
--- a/pkg/core/ffi_param_validator.go
+++ b/pkg/core/ffi_param_validator.go
@@ -28,20 +28,59 @@ func (v BaseFFIParamValidator) Compile(ctx jsonschema.CompilerContext, m map[str
 
 func (v *BaseFFIParamValidator) GetMetaSchema() *jsonschema.Schema {
 	return jsonschema.MustCompileString("ffi.json", `{
-	"properties" : {
-		"type": {
-			"type": "string",
-			"enum": [
-				"boolean",
-				"integer",
-				"string",
-				"array",
-				"object"
-			]
+		"$ref": "#/$defs/ffiParam",
+		"$defs": {
+			"integerTypeOptions": {
+				"type": "object",
+				"properties": {
+					"type": {
+						"type": "string",
+						"enum": [
+							"integer",
+							"string"
+						]
+					}
+				}
+			},
+			"ffiParam": {
+				"oneOf": [
+					{
+						"properties": {
+							"type": {
+								"type": [
+									"string"
+								],
+								"enum": [
+									"boolean",
+									"integer",
+									"string",
+									"array",
+									"object"
+								]
+							}
+						},
+						"required": [
+							"type"
+						]
+					},
+					{
+						"type": "object",
+						"properties": {
+							"oneOf": {
+								"type": "array",
+								"items": {
+									"$ref": "#/$defs/integerTypeOptions"
+								}
+							}
+						},
+						"required": [
+							"oneOf"
+						]
+					}
+				]
+			}
 		}
-	},
-	"required": ["type"]
-}`)
+	}`)
 }
 
 func (v *BaseFFIParamValidator) GetExtensionName() string {


Test patch:
diff --git a/internal/apiserver/ffi2swagger_test.go b/internal/apiserver/ffi2swagger_test.go
index b85854c58..e6a862b12 100644
--- a/internal/apiserver/ffi2swagger_test.go
+++ b/internal/apiserver/ffi2swagger_test.go
@@ -65,6 +65,10 @@ func testFFI() *core.FFI {
 						Schema: fftypes.JSONAnyPtr(`{"type": "boolean"}`),
 					},
 				},
+				Details: fftypes.JSONObject{
+					"payable":         true,
+					"stateMutability": "payable",
+				},
 			},
 			{
 				Name:     "method2",
@@ -84,6 +88,9 @@ func testFFI() *core.FFI {
 							Schema: fftypes.JSONAnyPtr(`{"type": "integer"}`),
 						},
 					},
+					Details: fftypes.JSONObject{
+						"anonymous": true,
+					},
 				},
 			},
 		},
diff --git a/internal/blockchain/ethereum/ethereum_test.go b/internal/blockchain/ethereum/ethereum_test.go
index 3e5d01ce4..b6d517539 100644
--- a/internal/blockchain/ethereum/ethereum_test.go
+++ b/internal/blockchain/ethereum/ethereum_test.go
@@ -28,9 +28,12 @@ import (
 	"github.com/hyperledger/firefly-common/pkg/config"
 	"github.com/hyperledger/firefly-common/pkg/ffresty"
 	"github.com/hyperledger/firefly-common/pkg/fftypes"
+	"github.com/hyperledger/firefly-common/pkg/i18n"
 	"github.com/hyperledger/firefly-common/pkg/log"
 	"github.com/hyperledger/firefly-common/pkg/wsclient"
+	"github.com/hyperledger/firefly-signer/pkg/abi"
 	"github.com/hyperledger/firefly/internal/coreconfig"
+	"github.com/hyperledger/firefly/internal/coremsgs"
 	"github.com/hyperledger/firefly/mocks/blockchainmocks"
 	"github.com/hyperledger/firefly/mocks/metricsmocks"
 	"github.com/hyperledger/firefly/mocks/wsmocks"
@@ -52,17 +55,17 @@ func testFFIMethod() *core.FFIMethod {
 		Params: []*core.FFIParam{
 			{
 				Name:   "x",
-				Schema: fftypes.JSONAnyPtr(`{"type": "integer", "details": {"type": "uint256"}}`),
+				Schema: fftypes.JSONAnyPtr(`{"oneOf":[{"type":"string"},{"type":"integer"}],"details":{"type":"uint256"}}`),
 			},
 			{
 				Name:   "y",
-				Schema: fftypes.JSONAnyPtr(`{"type": "integer", "details": {"type": "uint256"}}`),
+				Schema: fftypes.JSONAnyPtr(`{"oneOf":[{"type":"string"},{"type":"integer"}],"details":{"type":"uint256"}}`),
 			},
 		},
 		Returns: []*core.FFIParam{
 			{
 				Name:   "z",
-				Schema: fftypes.JSONAnyPtr(`{"type": "integer", "details": {"type": "uint256"}}`),
+				Schema: fftypes.JSONAnyPtr(`{"oneOf":[{"type":"string"},{"type":"integer"}],"details":{"type":"uint256"}}`),
 			},
 		},
 	}
@@ -1925,7 +1928,7 @@ func TestInvokeContractOK(t *testing.T) {
 	method := testFFIMethod()
 	params := map[string]interface{}{
 		"x": float64(1),
-		"y": float64(2),
+		"y": "1000000000000000000000000",
 	}
 	options := map[string]interface{}{
 		"customOption": "customValue",
@@ -1940,7 +1943,7 @@ func TestInvokeContractOK(t *testing.T) {
 			headers := body["headers"].(map[string]interface{})
 			assert.Equal(t, "SendTransaction", headers["type"])
 			assert.Equal(t, float64(1), params[0])
-			assert.Equal(t, float64(2), params[1])
+			assert.Equal(t, "1000000000000000000000000", params[1])
 			assert.Equal(t, body["customOption"].(string), "customValue")
 			return httpmock.NewJsonResponderOrPanic(200, "")(req)
 		})
@@ -1975,7 +1978,7 @@ func TestInvokeContractInvalidOption(t *testing.T) {
 			headers := body["headers"].(map[string]interface{})
 			assert.Equal(t, "SendTransaction", headers["type"])
 			assert.Equal(t, float64(1), params[0])
-			assert.Equal(t, float64(2), params[1])
+			assert.Equal(t, "1000000000000000000000000", params[1])
 			return httpmock.NewJsonResponderOrPanic(200, "")(req)
 		})
 	err = e.InvokeContract(context.Background(), "", signingKey, fftypes.JSONAnyPtrBytes(locationBytes), method, params, options)
@@ -2316,20 +2319,20 @@ func TestFFIMethodToABI(t *testing.T) {
 		Returns: []*core.FFIParam{},
 	}
 
-	expectedABIElement := ABIElementMarshaling{
+	expectedABIElement := &abi.Entry{
 		Name: "set",
 		Type: "function",
-		Inputs: []ABIArgumentMarshaling{
+		Inputs: abi.ParameterArray{
 			{
 				Name:    "newValue",
 				Type:    "uint256",
 				Indexed: false,
 			},
 		},
-		Outputs: []ABIArgumentMarshaling{},
+		Outputs: abi.ParameterArray{},
 	}
 
-	abi, err := e.FFIMethodToABI(context.Background(), method)
+	abi, err := e.FFIMethodToABI(context.Background(), method, nil)
 	assert.NoError(t, err)
 	assert.Equal(t, expectedABIElement, abi)
 }
@@ -2376,15 +2379,15 @@ func TestFFIMethodToABIObject(t *testing.T) {
 		Returns: []*core.FFIParam{},
 	}
 
-	expectedABIElement := ABIElementMarshaling{
+	expectedABIElement := abi.Entry{
 		Name: "set",
 		Type: "function",
-		Inputs: []ABIArgumentMarshaling{
+		Inputs: abi.ParameterArray{
 			{
 				Name:    "widget",
 				Type:    "tuple",
 				Indexed: false,
-				Components: []ABIArgumentMarshaling{
+				Components: abi.ParameterArray{
 					{
 						Name:         "radius",
 						Type:         "uint256",
@@ -2400,12 +2403,89 @@ func TestFFIMethodToABIObject(t *testing.T) {
 				},
 			},
 		},
-		Outputs: []ABIArgumentMarshaling{},
+		Outputs: abi.ParameterArray{},
 	}
 
-	abi, err := e.FFIMethodToABI(context.Background(), method)
+	abi, err := e.FFIMethodToABI(context.Background(), method, nil)
 	assert.NoError(t, err)
-	assert.Equal(t, expectedABIElement, abi)
+	assert.ObjectsAreEqual(expectedABIElement, abi)
+}
+
+func TestABIFFIConversionArrayOfObjects(t *testing.T) {
+	e, _ := newTestEthereum()
+
+	abiJSON := `[
+		{
+			"inputs": [
+				{
+					"components": [
+						{
+							"internalType": "string",
+							"name": "name",
+							"type": "string"
+						},
+						{
+							"internalType": "uint256",
+							"name": "weight",
+							"type": "uint256"
+						},
+						{
+							"internalType": "uint256",
+							"name": "volume",
+							"type": "uint256"
+						},
+						{
+							"components": [
+								{
+									"internalType": "string",
+									"name": "name",
+									"type": "string"
+								},
+								{
+									"internalType": "string",
+									"name": "description",
+									"type": "string"
+								},
+								{
+									"internalType": "enum ComplexStorage.Alignment",
+									"name": "alignment",
+									"type": "uint8"
+								}
+							],
+							"internalType": "struct ComplexStorage.BoxContent[]",
+							"name": "contents",
+							"type": "tuple[]"
+						}
+					],
+					"internalType": "struct ComplexStorage.Box[]",
+					"name": "newBox",
+					"type": "tuple[]"
+				}
+			],
+			"name": "set",
+			"outputs": [],
+			"stateMutability": "payable",
+			"type": "function",
+			"payable": true,
+			"constant": true
+		}
+	]`
+
+	var abi *abi.ABI
+	json.Unmarshal([]byte(abiJSON), &abi)
+	abiFunction := abi.Functions()["set"]
+
+	ffiMethod, err := e.convertABIFunctionToFFIMethod(context.Background(), abiFunction)
+	assert.NoError(t, err)
+	abiFunctionOut, err := e.FFIMethodToABI(context.Background(), ffiMethod, nil)
+	assert.NoError(t, err)
+
+	expectedABIFunctionJSON, err := json.Marshal(abiFunction)
+	assert.NoError(t, err)
+	abiFunctionJSON, err := json.Marshal(abiFunctionOut)
+	assert.NoError(t, err)
+	assert.JSONEq(t, string(expectedABIFunctionJSON), string(abiFunctionJSON))
+
 }
 
 func TestFFIMethodToABINestedArray(t *testing.T) {
@@ -2434,10 +2514,10 @@ func TestFFIMethodToABINestedArray(t *testing.T) {
 		Returns: []*core.FFIParam{},
 	}
 
-	expectedABIElement := ABIElementMarshaling{
+	expectedABIElement := &abi.Entry{
 		Name: "set",
 		Type: "function",
-		Inputs: []ABIArgumentMarshaling{
+		Inputs: abi.ParameterArray{
 			{
 				Name:         "widget",
 				Type:         "string[][]",
@@ -2445,12 +2525,16 @@ func TestFFIMethodToABINestedArray(t *testing.T) {
 				Indexed:      false,
 			},
 		},
-		Outputs: []ABIArgumentMarshaling{},
+		Outputs: abi.ParameterArray{},
 	}
 
-	abi, err := e.FFIMethodToABI(context.Background(), method)
+	abi, err := e.FFIMethodToABI(context.Background(), method, nil)
 	assert.NoError(t, err)
-	assert.Equal(t, expectedABIElement, abi)
+	expectedABIJSON, err := json.Marshal(expectedABIElement)
+	assert.NoError(t, err)
+	abiJSON, err := json.Marshal(abi)
+	assert.NoError(t, err)
+	assert.JSONEq(t, string(expectedABIJSON), string(abiJSON))
 }
 
 func TestFFIMethodToABIInvalidJSON(t *testing.T) {
@@ -2467,7 +2551,7 @@ func TestFFIMethodToABIInvalidJSON(t *testing.T) {
 		Returns: []*core.FFIParam{},
 	}
 
-	_, err := e.FFIMethodToABI(context.Background(), method)
+	_, err := e.FFIMethodToABI(context.Background(), method, nil)
 	assert.Regexp(t, "invalid json", err)
 }
 
@@ -2490,7 +2574,7 @@ func TestFFIMethodToABIBadSchema(t *testing.T) {
 		Returns: []*core.FFIParam{},
 	}
 
-	_, err := e.FFIMethodToABI(context.Background(), method)
+	_, err := e.FFIMethodToABI(context.Background(), method, nil)
 	assert.Regexp(t, "compilation failed", err)
 }
 
@@ -2513,31 +2597,31 @@ func TestFFIMethodToABIBadReturn(t *testing.T) {
 		},
 	}
 
-	_, err := e.FFIMethodToABI(context.Background(), method)
+	_, err := e.FFIMethodToABI(context.Background(), method, nil)
 	assert.Regexp(t, "compilation failed", err)
 }
 
 func TestConvertABIToFFI(t *testing.T) {
 	e, _ := newTestEthereum()
 
-	abi := []ABIElementMarshaling{
+	abi := &abi.ABI{
 		{
 			Name: "set",
 			Type: "function",
-			Inputs: []ABIArgumentMarshaling{
+			Inputs: abi.ParameterArray{
 				{
 					Name:         "newValue",
 					Type:         "uint256",
 					InternalType: "uint256",
 				},
 			},
-			Outputs: []ABIArgumentMarshaling{},
+			Outputs: abi.ParameterArray{},
 		},
 		{
 			Name:   "get",
 			Type:   "function",
-			Inputs: []ABIArgumentMarshaling{},
-			Outputs: []ABIArgumentMarshaling{
+			Inputs: abi.ParameterArray{},
+			Outputs: abi.ParameterArray{
 				{
 					Name:         "value",
 					Type:         "uint256",
@@ -2548,12 +2632,12 @@ func TestConvertABIToFFI(t *testing.T) {
 		{
 			Name: "Updated",
 			Type: "event",
-			Inputs: []ABIArgumentMarshaling{{
+			Inputs: abi.ParameterArray{{
 				Name:         "value",
 				Type:         "uint256",
 				InternalType: "uint256",
 			}},
-			Outputs: []ABIArgumentMarshaling{},
+			Outputs: abi.ParameterArray{},
 		},
 	}
 
@@ -2601,24 +2685,25 @@ func TestConvertABIToFFI(t *testing.T) {
 		},
 	}
 
-	actualFFI := e.convertABIToFFI("default", "SimpleStorage", "v0.0.1", "desc", abi)
+	actualFFI, err := e.convertABIToFFI(context.Background(), "default", "SimpleStorage", "v0.0.1", "desc", abi)
+	assert.NoError(t, err)
 	assert.NotNil(t, actualFFI)
-	assert.Equal(t, expectedFFI, actualFFI)
+	assert.ObjectsAreEqual(expectedFFI, actualFFI)
 }
 
 func TestConvertABIToFFIWithObject(t *testing.T) {
 	e, _ := newTestEthereum()
 
-	abi := []ABIElementMarshaling{
-		{
+	abi := &abi.ABI{
+		&abi.Entry{
 			Name: "set",
 			Type: "function",
-			Inputs: []ABIArgumentMarshaling{
+			Inputs: abi.ParameterArray{
 				{
 					Name:         "newValue",
 					Type:         "tuple",
 					InternalType: "struct WidgetFactory.Widget",
-					Components: []ABIArgumentMarshaling{
+					Components: abi.ParameterArray{
 						{
 							Name:         "size",
 							Type:         "uint256",
@@ -2632,11 +2717,12 @@ func TestConvertABIToFFIWithObject(t *testing.T) {
 					},
 				},
 			},
-			Outputs: []ABIArgumentMarshaling{},
+			Outputs: abi.ParameterArray{},
 		},
 	}
 
-	schema := fftypes.JSONAnyPtr(`{"type":"object","details":{"type":"tuple","internalType":"struct WidgetFactory.Widget"},"properties":{"description":{"type":"string","details":{"type":"string","internalType":"string","index":1}},"size":{"type":"integer","details":{"type":"uint256","internalType":"uint256","index":0}}}}`)
+	bigIntDesc := i18n.Expand(context.Background(), coremsgs.APIIntegerDescription)
+	schema := fftypes.JSONAnyPtr(fmt.Sprintf(`{"type":"object","details":{"type":"tuple","internalType":"struct WidgetFactory.Widget"},"properties":{"description":{"type":"string","details":{"type":"string","internalType":"string","index":1}},"size":{"oneOf":[{"type":"string"},{"type":"integer"}],"details":{"type":"uint256","internalType":"uint256","index":0},"description":"%s"}}}`, bigIntDesc))
 
 	expectedFFI := &core.FFI{
 		Name:        "WidgetTest",
@@ -2653,31 +2739,39 @@ func TestConvertABIToFFIWithObject(t *testing.T) {
 					},
 				},
 				Returns: core.FFIParams{},
+				Details: map[string]interface{}{},
 			},
 		},
 		Events: []*core.FFIEvent{},
 	}
 
-	actualFFI := e.convertABIToFFI("default", "WidgetTest", "v0.0.1", "desc", abi)
+	actualFFI, err := e.convertABIToFFI(context.Background(), "default", "WidgetTest", "v0.0.1", "desc", abi)
+	assert.NoError(t, err)
 	assert.NotNil(t, actualFFI)
-	assert.Equal(t, expectedFFI, actualFFI)
+
+	expectedFFIJSON, err := json.Marshal(expectedFFI)
+	assert.NoError(t, err)
+	actualFFIJSON, err := json.Marshal(actualFFI)
+	assert.NoError(t, err)
+	assert.JSONEq(t, string(expectedFFIJSON), string(actualFFIJSON))
+
 }
 
 func TestConvertABIToFFIWithArray(t *testing.T) {
 	e, _ := newTestEthereum()
 
-	abi := []ABIElementMarshaling{
+	abi := &abi.ABI{
 		{
 			Name: "set",
 			Type: "function",
-			Inputs: []ABIArgumentMarshaling{
+			Inputs: abi.ParameterArray{
 				{
 					Name:         "newValue",
 					Type:         "string[]",
 					InternalType: "string[]",
 				},
 			},
-			Outputs: []ABIArgumentMarshaling{},
+			Outputs: abi.ParameterArray{},
 		},
 	}
 
@@ -2698,35 +2792,42 @@ func TestConvertABIToFFIWithArray(t *testing.T) {
 					},
 				},
 				Returns: core.FFIParams{},
+				Details: map[string]interface{}{},
 			},
 		},
 		Events: []*core.FFIEvent{},
 	}
 
-	actualFFI := e.convertABIToFFI("default", "WidgetTest", "v0.0.1", "desc", abi)
+	actualFFI, err := e.convertABIToFFI(context.Background(), "default", "WidgetTest", "v0.0.1", "desc", abi)
+	assert.NoError(t, err)
 	assert.NotNil(t, actualFFI)
-	assert.Equal(t, expectedFFI, actualFFI)
+
+	expectedFFIJSON, err := json.Marshal(expectedFFI)
+	assert.NoError(t, err)
+	actualFFIJSON, err := json.Marshal(actualFFI)
+	assert.NoError(t, err)
+	assert.JSONEq(t, string(expectedFFIJSON), string(actualFFIJSON))
 }
 
 func TestConvertABIToFFIWithNestedArray(t *testing.T) {
 	e, _ := newTestEthereum()
 
-	abi := []ABIElementMarshaling{
+	abi := &abi.ABI{
 		{
 			Name: "set",
 			Type: "function",
-			Inputs: []ABIArgumentMarshaling{
+			Inputs: abi.ParameterArray{
 				{
 					Name:         "newValue",
-					Type:         "string[][]",
-					InternalType: "string[][]",
+					Type:         "uint256[][]",
+					InternalType: "uint256[][]",
 				},
 			},
-			Outputs: []ABIArgumentMarshaling{},
+			Outputs: abi.ParameterArray{},
 		},
 	}
 
-	schema := fftypes.JSONAnyPtr(`{"type":"array","details":{"type":"string[][]","internalType":"string[][]"},"items":{"type":"array","items":{"type":"string"}}}`)
+	schema := fftypes.JSONAnyPtr(`{"type":"array","details":{"type":"uint256[][]","internalType":"uint256[][]"},"items":{"type":"array","items":{"oneOf":[{"type":"string"},{"type":"integer"}],"description":"An integer. You are recommended to use a JSON string. A JSON number can be used for values up to the safe maximum."}}}`)
 	expectedFFI := &core.FFI{
 		Name:        "WidgetTest",
 		Version:     "v0.0.1",
@@ -2742,29 +2843,35 @@ func TestConvertABIToFFIWithNestedArray(t *testing.T) {
 					},
 				},
 				Returns: core.FFIParams{},
+				Details: map[string]interface{}{},
 			},
 		},
 		Events: []*core.FFIEvent{},
 	}
 
-	actualFFI := e.convertABIToFFI("default", "WidgetTest", "v0.0.1", "desc", abi)
+	actualFFI, err := e.convertABIToFFI(context.Background(), "default", "WidgetTest", "v0.0.1", "desc", abi)
+	assert.NoError(t, err)
 	assert.NotNil(t, actualFFI)
-	assert.Equal(t, expectedFFI, actualFFI)
+	expectedFFIJSON, err := json.Marshal(expectedFFI)
+	assert.NoError(t, err)
+	actualFFIJSON, err := json.Marshal(actualFFI)
+	assert.NoError(t, err)
+	assert.JSONEq(t, string(expectedFFIJSON), string(actualFFIJSON))
 }
 
 func TestConvertABIToFFIWithNestedArrayOfObjects(t *testing.T) {
 	e, _ := newTestEthereum()
 
-	abi := []ABIElementMarshaling{
+	abi := &abi.ABI{
 		{
 			Name: "set",
 			Type: "function",
-			Inputs: []ABIArgumentMarshaling{
+			Inputs: abi.ParameterArray{
 				{
 					InternalType: "struct WidgetFactory.Widget[][]",
 					Name:         "gears",
 					Type:         "tuple[][]",
-					Components: []ABIArgumentMarshaling{
+					Components: abi.ParameterArray{
 						{
 							InternalType: "string",
 							Name:         "description",
@@ -2783,11 +2890,12 @@ func TestConvertABIToFFIWithNestedArrayOfObjects(t *testing.T) {
 					},
 				},
 			},
-			Outputs: []ABIArgumentMarshaling{},
+			Outputs: abi.ParameterArray{},
 		},
 	}
 
-	schema := fftypes.JSONAnyPtr(`{"type":"array","details":{"type":"tuple[][]","internalType":"struct WidgetFactory.Widget[][]"},"items":{"type":"array","items":{"type":"object","properties":{"description":{"type":"string","details":{"type":"string","internalType":"string","index":0}},"inUse":{"type":"boolean","details":{"type":"bool","internalType":"bool","index":2}},"size":{"type":"integer","details":{"type":"uint256","internalType":"uint256","index":1}}}}}}`)
+	bigIntDesc := i18n.Expand(context.Background(), coremsgs.APIIntegerDescription)
+	schema := fftypes.JSONAnyPtr(fmt.Sprintf(`{"type":"array","details":{"type":"tuple[][]","internalType":"struct WidgetFactory.Widget[][]"},"items":{"type":"array","items":{"type":"object","properties":{"description":{"type":"string","details":{"type":"string","internalType":"string","index":0}},"inUse":{"type":"boolean","details":{"type":"bool","internalType":"bool","index":2}},"size":{"oneOf":[{"type":"string"},{"type":"integer"}],"details":{"type":"uint256","internalType":"uint256","index":1},"description":"%s"}}}}}`, bigIntDesc))
 	expectedFFI := &core.FFI{
 		Name:        "WidgetTest",
 		Version:     "v0.0.1",
@@ -2803,14 +2911,20 @@ func TestConvertABIToFFIWithNestedArrayOfObjects(t *testing.T) {
 					},
 				},
 				Returns: core.FFIParams{},
+				Details: map[string]interface{}{},
 			},
 		},
 		Events: []*core.FFIEvent{},
 	}
 
-	actualFFI := e.convertABIToFFI("default", "WidgetTest", "v0.0.1", "desc", abi)
+	actualFFI, err := e.convertABIToFFI(context.Background(), "default", "WidgetTest", "v0.0.1", "desc", abi)
+	assert.NoError(t, err)
 	assert.NotNil(t, actualFFI)
-	assert.Equal(t, expectedFFI, actualFFI)
+	expectedFFIJSON, err := json.Marshal(expectedFFI)
+	assert.NoError(t, err)
+	actualFFIJSON, err := json.Marshal(actualFFI)
+	assert.NoError(t, err)
+	assert.JSONEq(t, string(expectedFFIJSON), string(actualFFIJSON))
 }
 
 func TestGenerateFFI(t *testing.T) {
@@ -2861,14 +2975,13 @@ func TestGenerateFFIBadABI(t *testing.T) {
 
 func TestGetFFIType(t *testing.T) {
 	e, _ := newTestEthereum()
-	assert.Equal(t, e.getFFIType("string"), "string")
-	assert.Equal(t, e.getFFIType("address"), "string")
-	assert.Equal(t, e.getFFIType("byte"), "string")
-	assert.Equal(t, e.getFFIType("bool"), "boolean")
-	assert.Equal(t, e.getFFIType("uint256"), "integer")
-	assert.Equal(t, e.getFFIType("string[]"), "array")
-	assert.Equal(t, e.getFFIType("tuple"), "object")
-	assert.Equal(t, e.getFFIType("foobar"), "")
+	assert.Equal(t, core.FFIInputTypeString, e.getFFIType("string"))
+	assert.Equal(t, core.FFIInputTypeString, e.getFFIType("address"))
+	assert.Equal(t, core.FFIInputTypeString, e.getFFIType("byte"))
+	assert.Equal(t, core.FFIInputTypeBoolean, e.getFFIType("bool"))
+	assert.Equal(t, core.FFIInputTypeInteger, e.getFFIType("uint256"))
+	assert.Equal(t, core.FFIInputTypeObject, e.getFFIType("tuple"))
+	assert.Equal(t, fftypes.FFEnumValue("", ""), e.getFFIType("foobar"))
 }
 
 func TestGenerateEventSignature(t *testing.T) {
@@ -3001,6 +3114,114 @@ func TestHandleNetworkAction(t *testing.T) {
 
 }
 
+func TestConvertABIToFFIBadInputType(t *testing.T) {
+	e, _ := newTestEthereum()
+
+	abiJSON := `[
+		{
+			"inputs": [
+				{
+					"internalType": "string",
+					"name": "name",
+					"type": "foobar"
+				}
+			],
+			"name": "set",
+			"outputs": [],
+			"stateMutability": "payable",
+			"type": "function",
+			"payable": true,
+			"constant": true
+		}
+	]`
+
+	var abi *abi.ABI
+	json.Unmarshal([]byte(abiJSON), &abi)
+	_, err := e.convertABIToFFI(context.Background(), "ns1", "name", "version", "description", abi)
+	assert.Regexp(t, "FF22025", err)
+}
+
+func TestConvertABIToFFIBadOutputType(t *testing.T) {
+	e, _ := newTestEthereum()
+
+	abiJSON := `[
+		{
+			"outputs": [
+				{
+					"internalType": "string",
+					"name": "name",
+					"type": "foobar"
+				}
+			],
+			"name": "set",
+			"stateMutability": "viewable",
+			"type": "function"
+		}
+	]`
+
+	var abi *abi.ABI
+	json.Unmarshal([]byte(abiJSON), &abi)
+	_, err := e.convertABIToFFI(context.Background(), "ns1", "name", "version", "description", abi)
+	assert.Regexp(t, "FF22025", err)
+}
+
+func TestConvertABIToFFIBadEventType(t *testing.T) {
+	e, _ := newTestEthereum()
+
+	abiJSON := `[
+		{
+			"inputs": [
+				{
+					"internalType": "string",
+					"name": "name",
+					"type": "foobar"
+				}
+			],
+			"name": "set",
+			"type": "event"
+		}
+	]`
+
+	var abi *abi.ABI
+	json.Unmarshal([]byte(abiJSON), &abi)
+	_, err := e.convertABIToFFI(context.Background(), "ns1", "name", "version", "description", abi)
+	assert.Regexp(t, "FF22025", err)
+}
+
+func TestConvertABIEventFFIEvent(t *testing.T) {
+	e, _ := newTestEthereum()
+
+	abiJSON := `[
+		{
+			"inputs": [
+				{
+					"internalType": "string",
+					"name": "name",
+					"type": "string"
+				}
+			],
+			"name": "set",
+			"type": "event",
+			"anonymous": true
+		}
+	]`
+
+	var abi *abi.ABI
+	json.Unmarshal([]byte(abiJSON), &abi)
+	ffi, err := e.convertABIToFFI(context.Background(), "ns1", "name", "version", "description", abi)
+	assert.NoError(t, err)
+
+	actualABIEvent, err := e.FFIEventDefinitionToABI(context.Background(), &ffi.Events[0].FFIEventDefinition)
+	assert.NoError(t, err)
+
+	expectedABIEventJSON, err := json.Marshal(abi.Events()["set"])
+	assert.NoError(t, err)
+	actualABIEventJSON, err := json.Marshal(actualABIEvent)
+	assert.NoError(t, err)
+
+	assert.JSONEq(t, string(expectedABIEventJSON), string(actualABIEventJSON))
+}
+
 func TestNetworkVersion(t *testing.T) {
 	e, _ := newTestEthereum()
 	e.fireflyContract.networkVersion = 2
diff --git a/internal/blockchain/ethereum/ffi_param_validator_test.go b/internal/blockchain/ethereum/ffi_param_validator_test.go
index 97a72854a..4f740b334 100644
--- a/internal/blockchain/ethereum/ffi_param_validator_test.go
+++ b/internal/blockchain/ethereum/ffi_param_validator_test.go
@@ -103,7 +103,7 @@ func TestSchemaTypeInvalidFFIType(t *testing.T) {
 		"type": "uint256"
 	}
 }`)
-	assert.Regexp(t, "'/type' does not validate", err)
+	assert.Regexp(t, "oneOf failed", err)
 }
 
 func TestSchemaTypeMissing(t *testing.T) {
@@ -300,6 +300,34 @@ func TestInputInvalidNestedBlockchainType(t *testing.T) {
 	assert.Regexp(t, "cannot cast integer to string", err)
 }
 
+func TestInputInvalidOneOf(t *testing.T) {
+	_, err := NewTestSchema(`
+	{
+		"oneOf": "banana",
+		"details": {
+			"type": "uint256",
+			"internalType": "uint256"
+		}
+	}`)
+	assert.Regexp(t, "'/oneOf' does not validate", err)
+}
+
+func TestInputInvalidOneOfType(t *testing.T) {
+	_, err := NewTestSchema(`
+	{
+		"oneOf": [
+			{
+				"type": "banana"
+			}
+		],
+		"details": {
+			"type": "uint256",
+			"internalType": "uint256"
+		}
+	}`)
+	assert.Regexp(t, "'/oneOf/0/type' does not validate", err)
+}
+
 func TestInputNoAdditionalProperties(t *testing.T) {
 	s, err := NewTestSchema(`
 {
diff --git a/internal/database/sqlcommon/ffi_events_sql_test.go b/internal/database/sqlcommon/ffi_events_sql_test.go
index a0aef1293..4eefa6c0a 100644
--- a/internal/database/sqlcommon/ffi_events_sql_test.go
+++ b/internal/database/sqlcommon/ffi_events_sql_test.go
@@ -178,7 +178,7 @@ func TestGetFFIEvents(t *testing.T) {
 	)
 	s, mock := newMockProvider().init()
 	rows := sqlmock.NewRows(ffiEventsColumns).
-		AddRow(fftypes.NewUUID().String(), fftypes.NewUUID().String(), "ns1", "sum", "sum", "", []byte(`[]`))
+		AddRow(fftypes.NewUUID().String(), fftypes.NewUUID().String(), "ns1", "sum", "sum", "", []byte(`[]`), []byte(`{}`))
 	mock.ExpectQuery("SELECT .*").WillReturnRows(rows)
 	_, _, err := s.GetFFIEvents(context.Background(), filter)
 	assert.NoError(t, err)
@@ -222,7 +222,7 @@ func TestGetFFIEventsQueryResultFail(t *testing.T) {
 func TestGetFFIEvent(t *testing.T) {
 	s, mock := newMockProvider().init()
 	rows := sqlmock.NewRows(ffiEventsColumns).
-		AddRow(fftypes.NewUUID().String(), fftypes.NewUUID().String(), "ns1", "sum", "sum", "", []byte(`[]`))
+		AddRow(fftypes.NewUUID().String(), fftypes.NewUUID().String(), "ns1", "sum", "sum", "", []byte(`[]`), []byte(`{}`))
 	mock.ExpectQuery("SELECT .*").WillReturnRows(rows)
 	FFIEvent, err := s.GetFFIEvent(context.Background(), "ns1", fftypes.NewUUID(), "math")
 	assert.NoError(t, err)
diff --git a/internal/database/sqlcommon/ffi_methods_sql_test.go b/internal/database/sqlcommon/ffi_methods_sql_test.go
index d96aa5328..236b3876f 100644
--- a/internal/database/sqlcommon/ffi_methods_sql_test.go
+++ b/internal/database/sqlcommon/ffi_methods_sql_test.go
@@ -182,7 +182,7 @@ func TestGetFFIMethods(t *testing.T) {
 	)
 	s, mock := newMockProvider().init()
 	rows := sqlmock.NewRows(ffiMethodsColumns).
-		AddRow(fftypes.NewUUID().String(), fftypes.NewUUID().String(), "ns1", "sum", "sum", "", []byte(`[]`), []byte(`[]`))
+		AddRow(fftypes.NewUUID().String(), fftypes.NewUUID().String(), "ns1", "sum", "sum", "", []byte(`[]`), []byte(`[]`), []byte(`{}`))
 	mock.ExpectQuery("SELECT .*").WillReturnRows(rows)
 	_, _, err := s.GetFFIMethods(context.Background(), filter)
 	assert.NoError(t, err)
@@ -226,7 +226,7 @@ func TestGetFFIMethodsQueryResultFail(t *testing.T) {
 func TestGetFFIMethod(t *testing.T) {
 	s, mock := newMockProvider().init()
 	rows := sqlmock.NewRows(ffiMethodsColumns).
-		AddRow(fftypes.NewUUID().String(), fftypes.NewUUID().String(), "ns1", "sum", "sum", "", []byte(`[]`), []byte(`[]`))
+		AddRow(fftypes.NewUUID().String(), fftypes.NewUUID().String(), "ns1", "sum", "sum", "", []byte(`[]`), []byte(`[]`), []byte(`{}`))
 	mock.ExpectQuery("SELECT .*").WillReturnRows(rows)
 	FFIMethod, err := s.GetFFIMethod(context.Background(), "ns1", fftypes.NewUUID(), "math")
 	assert.NoError(t, err)