Repository: hyperledger/firefly
Pull request #866 (2022-06-17 14:03:46)

Move FFI/ABI conversion code to signer and common
This PR moves FFI related things out of core and into shared libraries so they can be used by other FireFly subprojects. Generic FFI types and validations have been moved to `firefly-common`. Ethereum specific FFI code has moved to `firefly-signer`.

Prerequisites for merge:
https://github.com/hyperledger/firefly-common/pull/19
https://github.com/hyperledger/firefly-signer/pull/9

TODO:
- [x] Update `go.mod` after release of `firefly-common` and `firefly-signer`


Patch:
diff --git a/go.mod b/go.mod
index 46d127585..e783af3b4 100644
--- a/go.mod
+++ b/go.mod
@@ -13,8 +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.10
-	github.com/hyperledger/firefly-signer v0.9.10
+	github.com/hyperledger/firefly-common v0.1.13
+	github.com/hyperledger/firefly-signer v0.9.12
 	github.com/jarcoal/httpmock v1.1.0
 	github.com/karlseguin/ccache v2.0.3+incompatible
 	github.com/lib/pq v1.10.6
diff --git a/go.sum b/go.sum
index 758f8d0bc..6058af980 100644
--- a/go.sum
+++ b/go.sum
@@ -448,7 +448,6 @@ 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=
@@ -727,11 +726,10 @@ 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.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/hyperledger/firefly-common v0.1.13 h1:eNK99U9FV43u1F46MM0mPuXT4Xn++orghpoTIIPsmwo=
+github.com/hyperledger/firefly-common v0.1.13/go.mod h1:2NqPi5Ud9H6rSlZXkLbotxW7z4EAD89p3/8oNOpm9Gs=
+github.com/hyperledger/firefly-signer v0.9.12 h1:pCPiGHx1+MbTsIQuRkoQmfWxvpcvtGHVavls0NnH0po=
+github.com/hyperledger/firefly-signer v0.9.12/go.mod h1:GPQRUZOFOAjkLmg8GDjZUjEdUD0gcar+CSVhwltIwyw=
 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=
@@ -1049,10 +1047,8 @@ 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=
@@ -1140,7 +1136,6 @@ 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=
@@ -1178,7 +1173,6 @@ 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=
@@ -1198,7 +1192,6 @@ 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=
@@ -1280,13 +1273,10 @@ 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=
@@ -1369,7 +1359,6 @@ 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=
@@ -1674,7 +1663,6 @@ 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 e69209bc0..f639f9cc0 100644
--- a/internal/apiserver/ffi2swagger.go
+++ b/internal/apiserver/ffi2swagger.go
@@ -36,7 +36,7 @@ import (
 )
 
 type FFISwaggerGen interface {
-	Generate(ctx context.Context, baseURL string, api *core.ContractAPI, ffi *core.FFI) *openapi3.T
+	Generate(ctx context.Context, baseURL string, api *core.ContractAPI, ffi *fftypes.FFI) *openapi3.T
 }
 
 type ContractListenerInput struct {
@@ -58,7 +58,7 @@ func NewFFISwaggerGen() FFISwaggerGen {
 	return &ffiSwaggerGen{}
 }
 
-func (og *ffiSwaggerGen) Generate(ctx context.Context, baseURL string, api *core.ContractAPI, ffi *core.FFI) (swagger *openapi3.T) {
+func (og *ffiSwaggerGen) Generate(ctx context.Context, baseURL string, api *core.ContractAPI, ffi *fftypes.FFI) (swagger *openapi3.T) {
 	hasLocation := !api.Location.IsNil()
 
 	routes := []*ffapi.Route{
@@ -67,7 +67,7 @@ func (og *ffiSwaggerGen) Generate(ctx context.Context, baseURL string, api *core
 			Path:            "interface", // must match a route defined in apiserver routes!
 			Method:          http.MethodGet,
 			JSONInputValue:  nil,
-			JSONOutputValue: func() interface{} { return &core.FFI{} },
+			JSONOutputValue: func() interface{} { return &fftypes.FFI{} },
 			JSONOutputCodes: []int{http.StatusOK},
 		},
 	}
@@ -87,7 +87,7 @@ func (og *ffiSwaggerGen) Generate(ctx context.Context, baseURL string, api *core
 	}).Generate(ctx, routes)
 }
 
-func (og *ffiSwaggerGen) addMethod(routes []*ffapi.Route, method *core.FFIMethod, hasLocation bool) []*ffapi.Route {
+func (og *ffiSwaggerGen) addMethod(routes []*ffapi.Route, method *fftypes.FFIMethod, hasLocation bool) []*ffapi.Route {
 	ctx := context.Background()
 	description := method.Description
 	if method.Details != nil && len(method.Details) > 0 {
@@ -95,27 +95,35 @@ func (og *ffiSwaggerGen) addMethod(routes []*ffapi.Route, method *core.FFIMethod
 		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() },
+		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, schemaGen ffapi.SchemaGenerator) (*openapi3.SchemaRef, error) {
+			return contractJSONSchema(&method.Params, hasLocation)
+		},
+		JSONOutputSchema: func(ctx context.Context, schemaGen ffapi.SchemaGenerator) (*openapi3.SchemaRef, error) {
+			return contractJSONSchema(&method.Returns, true)
+		},
 		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() },
+		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, schemaGen ffapi.SchemaGenerator) (*openapi3.SchemaRef, error) {
+			return contractJSONSchema(&method.Params, hasLocation)
+		},
+		JSONOutputSchema: func(ctx context.Context, schemaGen ffapi.SchemaGenerator) (*openapi3.SchemaRef, error) {
+			return contractJSONSchema(&method.Returns, true)
+		},
 		JSONOutputCodes:          []int{http.StatusOK},
 		PreTranslatedDescription: description,
 	})
 	return routes
 }
 
-func (og *ffiSwaggerGen) addEvent(routes []*ffapi.Route, event *core.FFIEvent, hasLocation bool) []*ffapi.Route {
+func (og *ffiSwaggerGen) addEvent(routes []*ffapi.Route, event *fftypes.FFIEvent, hasLocation bool) []*ffapi.Route {
 	ctx := context.Background()
 	description := event.Description
 	if event.Details != nil && len(event.Details) > 0 {
@@ -154,9 +162,17 @@ func (og *ffiSwaggerGen) addEvent(routes []*ffapi.Route, event *core.FFIEvent, h
  * Parse the FFI and build a corresponding JSON Schema to describe the request body for "invoke".
  * Returns the JSON Schema as an `fftypes.JSONObject`.
  */
-func contractCallJSONSchema(params *core.FFIParams, hasLocation bool) *fftypes.JSONObject {
+func contractJSONSchema(params *fftypes.FFIParams, hasLocation bool) (*openapi3.SchemaRef, error) {
+	paramSchema := make(fftypes.JSONObject, len(*params))
+	for _, param := range *params {
+		paramSchema[param.Name] = param.Schema
+	}
+	inputSchema := fftypes.JSONObject{
+		"type":       "object",
+		"properties": paramSchema,
+	}
 	properties := fftypes.JSONObject{
-		"input": ffiParamsJSONSchema(params),
+		"input": inputSchema,
 		"options": fftypes.JSONObject{
 			"type": "object",
 		},
@@ -164,29 +180,20 @@ func contractCallJSONSchema(params *core.FFIParams, hasLocation bool) *fftypes.J
 	if !hasLocation {
 		properties["location"] = fftypes.JSONAnyPtr(`{}`)
 	}
-	return &fftypes.JSONObject{
+	schema := fftypes.JSONObject{
 		"type":       "object",
 		"properties": properties,
 	}
-}
-
-func ffiParamsJSONSchema(params *core.FFIParams) *fftypes.JSONObject {
-	out := make(fftypes.JSONObject, len(*params))
-	for _, param := range *params {
-		out[param.Name] = ffiParamJSONSchema(param)
-	}
-	return &fftypes.JSONObject{
-		"type":       "object",
-		"properties": out,
+	b, err := json.Marshal(schema)
+	if err != nil {
+		return nil, err
 	}
-}
-
-func ffiParamJSONSchema(param *core.FFIParam) *fftypes.JSONObject {
-	out := fftypes.JSONObject{}
-	if err := json.Unmarshal(param.Schema.Bytes(), &out); err == nil {
-		return &out
+	s := openapi3.NewSchema()
+	err = s.UnmarshalJSON(b)
+	if err != nil {
+		return nil, err
 	}
-	return nil
+	return openapi3.NewSchemaRef("", s), nil
 }
 
 func buildDetailsTable(ctx context.Context, details map[string]interface{}) string {
diff --git a/internal/apiserver/route_get_contract_api_interface.go b/internal/apiserver/route_get_contract_api_interface.go
index 8ca6720ea..e7cbae6a8 100644
--- a/internal/apiserver/route_get_contract_api_interface.go
+++ b/internal/apiserver/route_get_contract_api_interface.go
@@ -20,8 +20,8 @@ import (
 	"net/http"
 
 	"github.com/hyperledger/firefly-common/pkg/ffapi"
+	"github.com/hyperledger/firefly-common/pkg/fftypes"
 	"github.com/hyperledger/firefly/internal/coremsgs"
-	"github.com/hyperledger/firefly/pkg/core"
 )
 
 var getContractAPIInterface = &ffapi.Route{
@@ -34,7 +34,7 @@ var getContractAPIInterface = &ffapi.Route{
 	QueryParams:     nil,
 	Description:     coremsgs.APIEndpointsGetContractAPIInterface,
 	JSONInputValue:  nil,
-	JSONOutputValue: func() interface{} { return &core.FFI{} },
+	JSONOutputValue: func() interface{} { return &fftypes.FFI{} },
 	JSONOutputCodes: []int{http.StatusOK},
 	Extensions: &coreExtensions{
 		CoreJSONHandler: func(r *ffapi.APIRequest, cr *coreRequest) (output interface{}, err error) {
diff --git a/internal/apiserver/route_get_contract_interface.go b/internal/apiserver/route_get_contract_interface.go
index 933b47da3..05365baa8 100644
--- a/internal/apiserver/route_get_contract_interface.go
+++ b/internal/apiserver/route_get_contract_interface.go
@@ -23,7 +23,6 @@ import (
 	"github.com/hyperledger/firefly-common/pkg/ffapi"
 	"github.com/hyperledger/firefly-common/pkg/fftypes"
 	"github.com/hyperledger/firefly/internal/coremsgs"
-	"github.com/hyperledger/firefly/pkg/core"
 )
 
 var getContractInterface = &ffapi.Route{
@@ -38,7 +37,7 @@ var getContractInterface = &ffapi.Route{
 	},
 	Description:     coremsgs.APIEndpointsGetContractInterface,
 	JSONInputValue:  nil,
-	JSONOutputValue: func() interface{} { return &core.FFI{} },
+	JSONOutputValue: func() interface{} { return &fftypes.FFI{} },
 	JSONOutputCodes: []int{http.StatusOK},
 	Extensions: &coreExtensions{
 		CoreJSONHandler: func(r *ffapi.APIRequest, cr *coreRequest) (output interface{}, err error) {
diff --git a/internal/apiserver/route_get_contract_interface_name_version.go b/internal/apiserver/route_get_contract_interface_name_version.go
index 26ad11d25..ac19838f5 100644
--- a/internal/apiserver/route_get_contract_interface_name_version.go
+++ b/internal/apiserver/route_get_contract_interface_name_version.go
@@ -21,8 +21,8 @@ import (
 	"strings"
 
 	"github.com/hyperledger/firefly-common/pkg/ffapi"
+	"github.com/hyperledger/firefly-common/pkg/fftypes"
 	"github.com/hyperledger/firefly/internal/coremsgs"
-	"github.com/hyperledger/firefly/pkg/core"
 )
 
 var getContractInterfaceNameVersion = &ffapi.Route{
@@ -38,7 +38,7 @@ var getContractInterfaceNameVersion = &ffapi.Route{
 	},
 	Description:     coremsgs.APIEndpointsGetContractInterfaceNameVersion,
 	JSONInputValue:  nil,
-	JSONOutputValue: func() interface{} { return &core.FFI{} },
+	JSONOutputValue: func() interface{} { return &fftypes.FFI{} },
 	JSONOutputCodes: []int{http.StatusOK},
 	Extensions: &coreExtensions{
 		CoreJSONHandler: func(r *ffapi.APIRequest, cr *coreRequest) (output interface{}, err error) {
diff --git a/internal/apiserver/route_get_contract_interfaces.go b/internal/apiserver/route_get_contract_interfaces.go
index f0af3da18..96617bc2b 100644
--- a/internal/apiserver/route_get_contract_interfaces.go
+++ b/internal/apiserver/route_get_contract_interfaces.go
@@ -20,8 +20,8 @@ import (
 	"net/http"
 
 	"github.com/hyperledger/firefly-common/pkg/ffapi"
+	"github.com/hyperledger/firefly-common/pkg/fftypes"
 	"github.com/hyperledger/firefly/internal/coremsgs"
-	"github.com/hyperledger/firefly/pkg/core"
 	"github.com/hyperledger/firefly/pkg/database"
 )
 
@@ -33,7 +33,7 @@ var getContractInterfaces = &ffapi.Route{
 	QueryParams:     nil,
 	Description:     coremsgs.APIEndpointsGetContractInterfaces,
 	JSONInputValue:  nil,
-	JSONOutputValue: func() interface{} { return []*core.FFI{} },
+	JSONOutputValue: func() interface{} { return []*fftypes.FFI{} },
 	JSONOutputCodes: []int{http.StatusOK},
 	Extensions: &coreExtensions{
 		FilterFactory: database.FFIQueryFactory,
diff --git a/internal/apiserver/route_post_contract_interface_generate.go b/internal/apiserver/route_post_contract_interface_generate.go
index a2577dc8f..ec0b52713 100644
--- a/internal/apiserver/route_post_contract_interface_generate.go
+++ b/internal/apiserver/route_post_contract_interface_generate.go
@@ -20,8 +20,8 @@ import (
 	"net/http"
 
 	"github.com/hyperledger/firefly-common/pkg/ffapi"
+	"github.com/hyperledger/firefly-common/pkg/fftypes"
 	"github.com/hyperledger/firefly/internal/coremsgs"
-	"github.com/hyperledger/firefly/pkg/core"
 )
 
 var postContractInterfaceGenerate = &ffapi.Route{
@@ -31,12 +31,12 @@ var postContractInterfaceGenerate = &ffapi.Route{
 	PathParams:      nil,
 	QueryParams:     []*ffapi.QueryParam{},
 	Description:     coremsgs.APIEndpointsPostContractInterfaceGenerate,
-	JSONInputValue:  func() interface{} { return &core.FFIGenerationRequest{} },
-	JSONOutputValue: func() interface{} { return &core.FFI{} },
+	JSONInputValue:  func() interface{} { return &fftypes.FFIGenerationRequest{} },
+	JSONOutputValue: func() interface{} { return &fftypes.FFI{} },
 	JSONOutputCodes: []int{http.StatusOK},
 	Extensions: &coreExtensions{
 		CoreJSONHandler: func(r *ffapi.APIRequest, cr *coreRequest) (output interface{}, err error) {
-			generationRequest := r.Input.(*core.FFIGenerationRequest)
+			generationRequest := r.Input.(*fftypes.FFIGenerationRequest)
 			return cr.or.Contracts().GenerateFFI(cr.ctx, generationRequest)
 		},
 	},
diff --git a/internal/apiserver/route_post_new_contract_interface.go b/internal/apiserver/route_post_new_contract_interface.go
index 90f62d7b1..4aecbbcc2 100644
--- a/internal/apiserver/route_post_new_contract_interface.go
+++ b/internal/apiserver/route_post_new_contract_interface.go
@@ -21,8 +21,8 @@ import (
 	"strings"
 
 	"github.com/hyperledger/firefly-common/pkg/ffapi"
+	"github.com/hyperledger/firefly-common/pkg/fftypes"
 	"github.com/hyperledger/firefly/internal/coremsgs"
-	"github.com/hyperledger/firefly/pkg/core"
 )
 
 var postNewContractInterface = &ffapi.Route{
@@ -34,14 +34,14 @@ var postNewContractInterface = &ffapi.Route{
 		{Name: "confirm", Description: coremsgs.APIConfirmQueryParam, IsBool: true, Example: "true"},
 	},
 	Description:     coremsgs.APIEndpointsPostNewContractInterface,
-	JSONInputValue:  func() interface{} { return &core.FFI{} },
-	JSONOutputValue: func() interface{} { return &core.FFI{} },
+	JSONInputValue:  func() interface{} { return &fftypes.FFI{} },
+	JSONOutputValue: func() interface{} { return &fftypes.FFI{} },
 	JSONOutputCodes: []int{http.StatusOK},
 	Extensions: &coreExtensions{
 		CoreJSONHandler: func(r *ffapi.APIRequest, cr *coreRequest) (output interface{}, err error) {
 			waitConfirm := strings.EqualFold(r.QP["confirm"], "true")
 			r.SuccessStatus = syncRetcode(waitConfirm)
-			ffi := r.Input.(*core.FFI)
+			ffi := r.Input.(*fftypes.FFI)
 			err = cr.or.DefinitionSender().DefineFFI(cr.ctx, ffi, waitConfirm)
 			return ffi, err
 		},
diff --git a/internal/assets/token_pool.go b/internal/assets/token_pool.go
index 1a38c6071..8dd4ba65b 100644
--- a/internal/assets/token_pool.go
+++ b/internal/assets/token_pool.go
@@ -29,7 +29,7 @@ import (
 )
 
 func (am *assetManager) CreateTokenPool(ctx context.Context, pool *core.TokenPool, waitConfirm bool) (*core.TokenPool, error) {
-	if err := core.ValidateFFNameFieldNoUUID(ctx, pool.Name, "name"); err != nil {
+	if err := fftypes.ValidateFFNameFieldNoUUID(ctx, pool.Name, "name"); err != nil {
 		return nil, err
 	}
 	if existing, err := am.database.GetTokenPool(ctx, am.namespace, pool.Name); err != nil {
@@ -141,7 +141,7 @@ func (am *assetManager) GetTokenPool(ctx context.Context, connector, poolName st
 	if _, err := am.selectTokenPlugin(ctx, connector); err != nil {
 		return nil, err
 	}
-	if err := core.ValidateFFNameFieldNoUUID(ctx, poolName, "name"); err != nil {
+	if err := fftypes.ValidateFFNameFieldNoUUID(ctx, poolName, "name"); err != nil {
 		return nil, err
 	}
 	pool, err := am.database.GetTokenPool(ctx, am.namespace, poolName)
@@ -159,7 +159,7 @@ func (am *assetManager) GetTokenPoolByNameOrID(ctx context.Context, poolNameOrID
 
 	poolID, err := fftypes.ParseUUID(ctx, poolNameOrID)
 	if err != nil {
-		if err := core.ValidateFFNameField(ctx, poolNameOrID, "name"); err != nil {
+		if err := fftypes.ValidateFFNameField(ctx, poolNameOrID, "name"); err != nil {
 			return nil, err
 		}
 		if pool, err = am.database.GetTokenPool(ctx, am.namespace, poolNameOrID); err != nil {
diff --git a/internal/blockchain/ethereum/ethereum.go b/internal/blockchain/ethereum/ethereum.go
index 9b301a657..f212c52a7 100644
--- a/internal/blockchain/ethereum/ethereum.go
+++ b/internal/blockchain/ethereum/ethereum.go
@@ -33,23 +33,15 @@ import (
 	"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-signer/pkg/ffi2abi"
 	"github.com/hyperledger/firefly/internal/coremsgs"
 	"github.com/hyperledger/firefly/internal/metrics"
 	"github.com/hyperledger/firefly/pkg/blockchain"
 	"github.com/hyperledger/firefly/pkg/core"
-	"github.com/santhosh-tekuri/jsonschema/v5"
 )
 
 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"
 )
 
 type Ethereum struct {
@@ -146,31 +138,6 @@ type Location struct {
 	Address string `json:"address"`
 }
 
-type paramDetails struct {
-	Type         string `json:"type"`
-	InternalType string `json:"internalType,omitempty"`
-	Indexed      bool   `json:"indexed,omitempty"`
-	Index        *int   `json:"index,omitempty"`
-}
-
-type Schema struct {
-	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 {
-	b, _ := json.Marshal(s)
-	return string(b)
-}
-
 type EthconnectMessageRequest struct {
 	Headers EthconnectMessageHeaders `json:"headers,omitempty"`
 	To      string                   `json:"to"`
@@ -733,7 +700,7 @@ func (e *Ethereum) SubmitNetworkAction(ctx context.Context, nsOpID string, signi
 	return e.invokeContractMethod(ctx, ethLocation.Address, signingKey, batchPinMethodABI, nsOpID, input, nil)
 }
 
-func (e *Ethereum) InvokeContract(ctx context.Context, nsOpID string, signingKey string, location *fftypes.JSONAny, method *core.FFIMethod, input map[string]interface{}, options map[string]interface{}) error {
+func (e *Ethereum) InvokeContract(ctx context.Context, nsOpID string, signingKey string, location *fftypes.JSONAny, method *fftypes.FFIMethod, input map[string]interface{}, options map[string]interface{}) error {
 	ethereumLocation, err := parseContractLocation(ctx, location)
 	if err != nil {
 		return err
@@ -745,7 +712,7 @@ func (e *Ethereum) InvokeContract(ctx context.Context, nsOpID string, signingKey
 	return e.invokeContractMethod(ctx, ethereumLocation.Address, signingKey, abi, nsOpID, orderedInput, options)
 }
 
-func (e *Ethereum) QueryContract(ctx context.Context, location *fftypes.JSONAny, method *core.FFIMethod, input map[string]interface{}, options map[string]interface{}) (interface{}, error) {
+func (e *Ethereum) QueryContract(ctx context.Context, location *fftypes.JSONAny, method *fftypes.FFIMethod, input map[string]interface{}, options map[string]interface{}) (interface{}, error) {
 	ethereumLocation, err := parseContractLocation(ctx, location)
 	if err != nil {
 		return nil, err
@@ -801,13 +768,17 @@ func (e *Ethereum) AddContractListener(ctx context.Context, listener *core.Contr
 	if err != nil {
 		return err
 	}
-	abi, err := e.FFIEventDefinitionToABI(ctx, &listener.Event.FFIEventDefinition)
+	abi, err := ffi2abi.ConvertFFIEventDefinitionToABI(ctx, &listener.Event.FFIEventDefinition)
 	if err != nil {
 		return i18n.WrapError(ctx, err, coremsgs.MsgContractParamInvalid)
 	}
 
 	subName := fmt.Sprintf("ff-sub-%s", listener.ID)
-	result, err := e.streams.createSubscription(ctx, location, e.streamID, subName, listener.Options.FirstEvent, abi)
+	firstEvent := string(core.SubOptsFirstEventNewest)
+	if listener.Options != nil {
+		firstEvent = listener.Options.FirstEvent
+	}
+	result, err := e.streams.createSubscription(ctx, location, e.streamID, subName, firstEvent, abi)
 	if err != nil {
 		return err
 	}
@@ -819,157 +790,21 @@ func (e *Ethereum) DeleteContractListener(ctx context.Context, subscription *cor
 	return e.streams.deleteSubscription(ctx, subscription.BackendID)
 }
 
-func (e *Ethereum) GetFFIParamValidator(ctx context.Context) (core.FFIParamValidator, error) {
-	return &FFIParamValidator{}, nil
-}
-
-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: abiInputs,
-	}
-	if event.Details != nil {
-		abiEntry.Anonymous = event.Details.GetBool("anonymous")
-	}
-	return abiEntry, nil
+func (e *Ethereum) GetFFIParamValidator(ctx context.Context) (fftypes.FFIParamValidator, error) {
+	return &ffi2abi.ParamValidator{}, nil
 }
 
-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
-	}
-
-	abiOutputs, err := e.convertFFIParamsToABIParameters(ctx, method.Returns)
-	if err != nil {
-		return nil, err
-	}
-	abiEntry := &abi.Entry{
-		Name:    method.Name,
-		Type:    "function",
-		Inputs:  abiInputs,
-		Outputs: abiOutputs,
-	}
-	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 abi.ParameterArray) string {
-	if strings.HasPrefix(typeName, "tuple") {
-		suffix := typeName[5:]
-		children := make([]string, len(components))
-		for i, component := range components {
-			children[i] = ABIArgumentToTypeString(component.Type, nil)
-		}
-		return "(" + strings.Join(children, ",") + ")" + suffix
-	}
-	return typeName
-}
-
-func ABIMethodToSignature(abi *abi.Entry) string {
-	result := abi.Name + "("
-	if len(abi.Inputs) > 0 {
-		types := make([]string, len(abi.Inputs))
-		for i, param := range abi.Inputs {
-			types[i] = ABIArgumentToTypeString(param.Type, param.Components)
-		}
-		result += strings.Join(types, ",")
-	}
-	result += ")"
-	return result
-}
-
-func (e *Ethereum) GenerateEventSignature(ctx context.Context, event *core.FFIEventDefinition) string {
-	abi, err := e.FFIEventDefinitionToABI(ctx, event)
+func (e *Ethereum) GenerateEventSignature(ctx context.Context, event *fftypes.FFIEventDefinition) string {
+	abi, err := ffi2abi.ConvertFFIEventDefinitionToABI(ctx, event)
 	if err != nil {
 		return ""
 	}
-	return ABIMethodToSignature(abi)
-}
-
-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 nil, err
-		}
-		s, err := c.Compile(param.Name)
-		if err != nil {
-			return nil, err
-		}
-		abiParamList[i] = processField(param.Name, s)
-	}
-	return abiParamList, nil
-}
-
-func processField(name string, schema *jsonschema.Schema) *abi.Parameter {
-	details := getParamDetails(schema)
-	parameter := &abi.Parameter{
-		Name:         name,
-		Type:         details.Type,
-		InternalType: details.InternalType,
-		Indexed:      details.Indexed,
-	}
-	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 parameter
+	return ffi2abi.ABIMethodToSignature(abi)
 }
 
-func buildABIParameterArrayForObject(properties map[string]*jsonschema.Schema) abi.ParameterArray {
-	parameters := make(abi.ParameterArray, len(properties))
-	for propertyName, propertySchema := range properties {
-		details := getParamDetails(propertySchema)
-		parameter := processField(propertyName, propertySchema)
-		parameters[*details.Index] = parameter
-	}
-	return parameters
-}
-
-func getParamDetails(schema *jsonschema.Schema) *paramDetails {
-	ext := schema.Extensions["details"]
-	details := ext.(detailsSchema)
-	blockchainType := details["type"].(string)
-	paramDetails := &paramDetails{
-		Type: blockchainType,
-	}
-	if i, ok := details["index"]; ok {
-		index, _ := i.(json.Number).Int64()
-		paramDetails.Index = new(int)
-		*paramDetails.Index = int(index)
-	}
-	if i, ok := details["indexed"]; ok {
-		paramDetails.Indexed = i.(bool)
-	}
-	if i, ok := details["internalType"]; ok {
-		paramDetails.InternalType = i.(string)
-	}
-	return paramDetails
-}
-
-func (e *Ethereum) prepareRequest(ctx context.Context, method *core.FFIMethod, input map[string]interface{}) (*abi.Entry, []interface{}, error) {
+func (e *Ethereum) prepareRequest(ctx context.Context, method *fftypes.FFIMethod, input map[string]interface{}) (*abi.Entry, []interface{}, error) {
 	orderedInput := make([]interface{}, len(method.Params))
-	abi, err := e.FFIMethodToABI(ctx, method, input)
+	abi, err := ffi2abi.ConvertFFIMethodToABI(ctx, method)
 	if err != nil {
 		return abi, orderedInput, err
 	}
@@ -994,7 +829,7 @@ func (e *Ethereum) getContractAddress(ctx context.Context, instancePath string)
 	return output["address"], nil
 }
 
-func (e *Ethereum) GenerateFFI(ctx context.Context, generationRequest *core.FFIGenerationRequest) (*core.FFI, error) {
+func (e *Ethereum) GenerateFFI(ctx context.Context, generationRequest *fftypes.FFIGenerationRequest) (*fftypes.FFI, error) {
 	var input FFIGenerationInput
 	err := json.Unmarshal(generationRequest.Input.Bytes(), &input)
 	if err != nil {
@@ -1003,167 +838,7 @@ func (e *Ethereum) GenerateFFI(ctx context.Context, generationRequest *core.FFIG
 	if len(*input.ABI) == 0 {
 		return nil, i18n.NewError(ctx, coremsgs.MsgFFIGenerationFailed, "ABI is empty")
 	}
-	return e.convertABIToFFI(ctx, generationRequest.Namespace, generationRequest.Name, generationRequest.Version, generationRequest.Description, input.ABI)
-}
-
-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:     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, nil
-}
-
-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:   input.Name,
-			Schema: fftypes.JSONAnyPtr(schema.ToJSON()),
-		}
-		params[i] = param
-	}
-	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
-	}
-	if abiFunction.StateMutability != "" {
-		details["stateMutability"] = string(abiFunction.StateMutability)
-	}
-	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
-		}
-		schema := e.getSchemaForABIInput(ctx, typeComponent)
-		param := &core.FFIParam{
-			Name:   output.Name,
-			Schema: fftypes.JSONAnyPtr(schema.ToJSON()),
-		}
-		params[i] = param
-	}
-	if abiEvent.Anonymous {
-		details["anonymous"] = true
-	}
-	return &core.FFIEvent{
-		FFIEventDefinition: core.FFIEventDefinition{
-			Name:    abiEvent.Name,
-			Params:  params,
-			Details: details,
-		},
-	}, nil
-}
-
-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,
-		},
-	}
-	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(solidityType string) core.FFIInputType {
-	switch solidityType {
-	case stringType, "address":
-		return core.FFIInputTypeString
-	case boolType:
-		return core.FFIInputTypeBoolean
-	case tupleType:
-		return core.FFIInputTypeObject
-	default:
-		switch {
-		case strings.Contains(solidityType, "byte"):
-			return core.FFIInputTypeString
-		case strings.Contains(solidityType, "int"):
-			return core.FFIInputTypeInteger
-		}
-	}
-	return ""
+	return ffi2abi.ConvertABIToFFI(ctx, generationRequest.Namespace, generationRequest.Name, generationRequest.Version, generationRequest.Description, input.ABI)
 }
 
 func (e *Ethereum) GetNetworkVersion(ctx context.Context, location *fftypes.JSONAny) (int, error) {
diff --git a/internal/blockchain/ethereum/ffi_param_validator.go b/internal/blockchain/ethereum/ffi_param_validator.go
deleted file mode 100644
index 898b94e2a..000000000
--- a/internal/blockchain/ethereum/ffi_param_validator.go
+++ /dev/null
@@ -1,251 +0,0 @@
-// Copyright © 2022 Kaleido, Inc.
-//
-// SPDX-License-Identifier: Apache-2.0
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package ethereum
-
-import (
-	"fmt"
-	"regexp"
-	"strconv"
-	"strings"
-
-	"github.com/santhosh-tekuri/jsonschema/v5"
-)
-
-type FFIParamValidator struct{}
-
-var intRegex, _ = regexp.Compile("^u?int([0-9]{1,3})$")
-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, 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 integerType:
-			if !isEthereumNumberType(blockchainType) {
-				valid = false
-			}
-		case booleanType:
-			if blockchainType != boolType {
-				valid = false
-			}
-		case arrayType:
-			if !strings.HasSuffix(blockchainType, "[]") {
-				valid = false
-			}
-		case objectType:
-			if blockchainType != tupleType {
-				valid = false
-			}
-		}
-
-		if valid {
-			return detailsSchema(n), nil
-		}
-		return nil, fmt.Errorf("cannot cast %v to %v", jsonType, blockchainType)
-	}
-	return nil, nil
-}
-
-func (v *FFIParamValidator) GetMetaSchema() *jsonschema.Schema {
-	return jsonschema.MustCompileString("ffiParamDetails.json", `{
-		"$ref": "#/$defs/ethereumParam",
-		"$defs": {
-			"ethereumParam": {
-				"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"
-						]
-					},
-					{
-						"type": "object",
-						"properties": {
-							"type": {
-								"const": "object"
-							},
-							"details": {
-								"$ref": "#/$defs/objectFieldDetails"
-							},
-							"properties": {
-								"type": "object",
-								"patternProperties": {
-									".*": {
-										"$ref": "#/$defs/ethereumObjectChildParam"
-									}
-								}
-							}
-						},
-						"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"
-				]
-			}
-		}
-	}`)
-}
-
-func (v *FFIParamValidator) GetExtensionName() string {
-	return "details"
-}
-
-type detailsSchema map[string]interface{}
-
-func (s detailsSchema) Validate(ctx jsonschema.ValidationContext, v interface{}) error {
-	// TODO: Additional validation of actual input possible in the future
-	return nil
-}
-
-func isEthereumNumberType(input string) bool {
-	matches := intRegex.FindStringSubmatch(input)
-	if len(matches) == 2 {
-		i, err := strconv.ParseInt(matches[1], 10, 0)
-		if err == nil && i >= 8 && i <= 256 && i%8 == 0 {
-			// valid
-			return true
-		}
-	}
-	return false
-}
-
-func isEthereumBytesType(input string) bool {
-	matches := bytesRegex.FindStringSubmatch(input)
-	if len(matches) == 2 {
-		if matches[1] == "" {
-			return true
-		}
-		i, err := strconv.ParseInt(matches[1], 10, 0)
-		if err == nil && i >= 1 && i <= 32 {
-			// valid
-			return true
-		}
-	}
-	return false
-}
diff --git a/internal/blockchain/fabric/fabric.go b/internal/blockchain/fabric/fabric.go
index 3bbd3a0e1..a8d2625ff 100644
--- a/internal/blockchain/fabric/fabric.go
+++ b/internal/blockchain/fabric/fabric.go
@@ -744,7 +744,7 @@ func (f *Fabric) buildFabconnectRequestBody(ctx context.Context, channel, chainc
 	return body, nil
 }
 
-func (f *Fabric) InvokeContract(ctx context.Context, nsOpID string, signingKey string, location *fftypes.JSONAny, method *core.FFIMethod, input map[string]interface{}, options map[string]interface{}) error {
+func (f *Fabric) InvokeContract(ctx context.Context, nsOpID string, signingKey string, location *fftypes.JSONAny, method *fftypes.FFIMethod, input map[string]interface{}, options map[string]interface{}) error {
 	fabricOnChainLocation, err := parseContractLocation(ctx, location)
 	if err != nil {
 		return err
@@ -767,7 +767,7 @@ func (f *Fabric) InvokeContract(ctx context.Context, nsOpID string, signingKey s
 	return f.invokeContractMethod(ctx, fabricOnChainLocation.Channel, fabricOnChainLocation.Chaincode, method.Name, signingKey, nsOpID, prefixItems, input, options)
 }
 
-func (f *Fabric) QueryContract(ctx context.Context, location *fftypes.JSONAny, method *core.FFIMethod, input map[string]interface{}, options map[string]interface{}) (interface{}, error) {
+func (f *Fabric) QueryContract(ctx context.Context, location *fftypes.JSONAny, method *fftypes.FFIMethod, input map[string]interface{}, options map[string]interface{}) (interface{}, error) {
 	fabricOnChainLocation, err := parseContractLocation(ctx, location)
 	if err != nil {
 		return nil, err
@@ -864,16 +864,16 @@ func (f *Fabric) DeleteContractListener(ctx context.Context, subscription *core.
 	return f.streams.deleteSubscription(ctx, subscription.BackendID)
 }
 
-func (f *Fabric) GetFFIParamValidator(ctx context.Context) (core.FFIParamValidator, error) {
+func (f *Fabric) GetFFIParamValidator(ctx context.Context) (fftypes.FFIParamValidator, error) {
 	// Fabconnect does not require any additional validation beyond "JSON Schema correctness" at this time
 	return nil, nil
 }
 
-func (f *Fabric) GenerateFFI(ctx context.Context, generationRequest *core.FFIGenerationRequest) (*core.FFI, error) {
+func (f *Fabric) GenerateFFI(ctx context.Context, generationRequest *fftypes.FFIGenerationRequest) (*fftypes.FFI, error) {
 	return nil, i18n.NewError(ctx, coremsgs.MsgFFIGenerationUnsupported)
 }
 
-func (f *Fabric) GenerateEventSignature(ctx context.Context, event *core.FFIEventDefinition) string {
+func (f *Fabric) GenerateEventSignature(ctx context.Context, event *fftypes.FFIEventDefinition) string {
 	return event.Name
 }
 
diff --git a/internal/contracts/manager.go b/internal/contracts/manager.go
index 394774cc6..d01238b8b 100644
--- a/internal/contracts/manager.go
+++ b/internal/contracts/manager.go
@@ -37,17 +37,17 @@ import (
 type Manager interface {
 	core.Named
 
-	GetFFI(ctx context.Context, name, version string) (*core.FFI, error)
-	GetFFIWithChildren(ctx context.Context, name, version string) (*core.FFI, error)
-	GetFFIByID(ctx context.Context, id *fftypes.UUID) (*core.FFI, error)
-	GetFFIByIDWithChildren(ctx context.Context, id *fftypes.UUID) (*core.FFI, error)
-	GetFFIs(ctx context.Context, filter database.AndFilter) ([]*core.FFI, *database.FilterResult, error)
-	ResolveFFI(ctx context.Context, ffi *core.FFI) error
+	GetFFI(ctx context.Context, name, version string) (*fftypes.FFI, error)
+	GetFFIWithChildren(ctx context.Context, name, version string) (*fftypes.FFI, error)
+	GetFFIByID(ctx context.Context, id *fftypes.UUID) (*fftypes.FFI, error)
+	GetFFIByIDWithChildren(ctx context.Context, id *fftypes.UUID) (*fftypes.FFI, error)
+	GetFFIs(ctx context.Context, filter database.AndFilter) ([]*fftypes.FFI, *database.FilterResult, error)
+	ResolveFFI(ctx context.Context, ffi *fftypes.FFI) error
 
 	InvokeContract(ctx context.Context, req *core.ContractCallRequest, waitConfirm bool) (interface{}, error)
 	InvokeContractAPI(ctx context.Context, apiName, methodPath string, req *core.ContractCallRequest, waitConfirm bool) (interface{}, error)
 	GetContractAPI(ctx context.Context, httpServerURL, apiName string) (*core.ContractAPI, error)
-	GetContractAPIInterface(ctx context.Context, apiName string) (*core.FFI, error)
+	GetContractAPIInterface(ctx context.Context, apiName string) (*fftypes.FFI, error)
 	GetContractAPIs(ctx context.Context, httpServerURL string, filter database.AndFilter) ([]*core.ContractAPI, *database.FilterResult, error)
 	ResolveContractAPI(ctx context.Context, httpServerURL string, api *core.ContractAPI) error
 
@@ -57,7 +57,7 @@ type Manager interface {
 	GetContractListeners(ctx context.Context, filter database.AndFilter) ([]*core.ContractListener, *database.FilterResult, error)
 	GetContractAPIListeners(ctx context.Context, apiName, eventPath string, filter database.AndFilter) ([]*core.ContractListener, *database.FilterResult, error)
 	DeleteContractListenerByNameOrID(ctx context.Context, nameOrID string) error
-	GenerateFFI(ctx context.Context, generationRequest *core.FFIGenerationRequest) (*core.FFI, error)
+	GenerateFFI(ctx context.Context, generationRequest *fftypes.FFIGenerationRequest) (*fftypes.FFI, error)
 
 	// From operations.OperationHandler
 	PrepareOperation(ctx context.Context, op *core.Operation) (*core.PreparedOperation, error)
@@ -70,7 +70,7 @@ type contractManager struct {
 	txHelper          txcommon.Helper
 	identity          identity.Manager
 	blockchain        blockchain.Plugin
-	ffiParamValidator core.FFIParamValidator
+	ffiParamValidator fftypes.FFIParamValidator
 	operations        operations.Manager
 	syncasync         syncasync.Bridge
 }
@@ -107,18 +107,18 @@ func (cm *contractManager) Name() string {
 }
 
 func (cm *contractManager) newFFISchemaCompiler() *jsonschema.Compiler {
-	c := core.NewFFISchemaCompiler()
+	c := fftypes.NewFFISchemaCompiler()
 	if cm.ffiParamValidator != nil {
 		c.RegisterExtension(cm.ffiParamValidator.GetExtensionName(), cm.ffiParamValidator.GetMetaSchema(), cm.ffiParamValidator)
 	}
 	return c
 }
 
-func (cm *contractManager) GetFFI(ctx context.Context, name, version string) (*core.FFI, error) {
+func (cm *contractManager) GetFFI(ctx context.Context, name, version string) (*fftypes.FFI, error) {
 	return cm.database.GetFFI(ctx, cm.namespace, name, version)
 }
 
-func (cm *contractManager) GetFFIWithChildren(ctx context.Context, name, version string) (*core.FFI, error) {
+func (cm *contractManager) GetFFIWithChildren(ctx context.Context, name, version string) (*fftypes.FFI, error) {
 	ffi, err := cm.GetFFI(ctx, name, version)
 	if err == nil {
 		err = cm.getFFIChildren(ctx, ffi)
@@ -126,11 +126,11 @@ func (cm *contractManager) GetFFIWithChildren(ctx context.Context, name, version
 	return ffi, err
 }
 
-func (cm *contractManager) GetFFIByID(ctx context.Context, id *fftypes.UUID) (*core.FFI, error) {
+func (cm *contractManager) GetFFIByID(ctx context.Context, id *fftypes.UUID) (*fftypes.FFI, error) {
 	return cm.database.GetFFIByID(ctx, cm.namespace, id)
 }
 
-func (cm *contractManager) getFFIChildren(ctx context.Context, ffi *core.FFI) (err error) {
+func (cm *contractManager) getFFIChildren(ctx context.Context, ffi *fftypes.FFI) (err error) {
 	mfb := database.FFIMethodQueryFactory.NewFilter(ctx)
 	ffi.Methods, _, err = cm.database.GetFFIMethods(ctx, cm.namespace, mfb.Eq("interface", ffi.ID))
 	if err != nil {
@@ -149,7 +149,7 @@ func (cm *contractManager) getFFIChildren(ctx context.Context, ffi *core.FFI) (e
 	return nil
 }
 
-func (cm *contractManager) GetFFIByIDWithChildren(ctx context.Context, id *fftypes.UUID) (ffi *core.FFI, err error) {
+func (cm *contractManager) GetFFIByIDWithChildren(ctx context.Context, id *fftypes.UUID) (ffi *fftypes.FFI, err error) {
 	err = cm.database.RunAsGroup(ctx, func(ctx context.Context) (err error) {
 		ffi, err = cm.database.GetFFIByID(ctx, cm.namespace, id)
 		if err != nil || ffi == nil {
@@ -160,7 +160,7 @@ func (cm *contractManager) GetFFIByIDWithChildren(ctx context.Context, id *fftyp
 	return ffi, err
 }
 
-func (cm *contractManager) GetFFIs(ctx context.Context, filter database.AndFilter) ([]*core.FFI, *database.FilterResult, error) {
+func (cm *contractManager) GetFFIs(ctx context.Context, filter database.AndFilter) ([]*fftypes.FFI, *database.FilterResult, error) {
 	return cm.database.GetFFIs(ctx, cm.namespace, filter)
 }
 
@@ -268,7 +268,7 @@ func (cm *contractManager) GetContractAPI(ctx context.Context, httpServerURL, ap
 	return api, err
 }
 
-func (cm *contractManager) GetContractAPIInterface(ctx context.Context, apiName string) (*core.FFI, error) {
+func (cm *contractManager) GetContractAPIInterface(ctx context.Context, apiName string) (*fftypes.FFI, error) {
 	api, err := cm.GetContractAPI(ctx, "", apiName)
 	if err != nil || api == nil {
 		return nil, err
@@ -314,7 +314,7 @@ func (cm *contractManager) ResolveContractAPI(ctx context.Context, httpServerURL
 	return nil
 }
 
-func (cm *contractManager) resolveFFIReference(ctx context.Context, ref *core.FFIReference) error {
+func (cm *contractManager) resolveFFIReference(ctx context.Context, ref *fftypes.FFIReference) error {
 	switch {
 	case ref == nil:
 		return i18n.NewError(ctx, coremsgs.MsgContractInterfaceNotFound, "")
@@ -355,7 +355,7 @@ func (cm *contractManager) uniquePathName(name string, usedNames map[string]bool
 	}
 }
 
-func (cm *contractManager) ResolveFFI(ctx context.Context, ffi *core.FFI) error {
+func (cm *contractManager) ResolveFFI(ctx context.Context, ffi *fftypes.FFI) error {
 	if err := ffi.Validate(ctx, false); err != nil {
 		return err
 	}
@@ -387,7 +387,7 @@ func (cm *contractManager) ResolveFFI(ctx context.Context, ffi *core.FFI) error
 	return nil
 }
 
-func (cm *contractManager) validateFFIMethod(ctx context.Context, method *core.FFIMethod) error {
+func (cm *contractManager) validateFFIMethod(ctx context.Context, method *fftypes.FFIMethod) error {
 	if method.Name == "" {
 		return i18n.NewError(ctx, coremsgs.MsgMethodNameMustBeSet)
 	}
@@ -404,7 +404,7 @@ func (cm *contractManager) validateFFIMethod(ctx context.Context, method *core.F
 	return nil
 }
 
-func (cm *contractManager) validateFFIParam(ctx context.Context, param *core.FFIParam) error {
+func (cm *contractManager) validateFFIParam(ctx context.Context, param *fftypes.FFIParam) error {
 	c := cm.newFFISchemaCompiler()
 	if err := c.AddResource(param.Name, strings.NewReader(param.Schema.String())); err != nil {
 		return i18n.WrapError(ctx, err, coremsgs.MsgFFISchemaParseFail, param.Name)
@@ -415,7 +415,7 @@ func (cm *contractManager) validateFFIParam(ctx context.Context, param *core.FFI
 	return nil
 }
 
-func (cm *contractManager) validateFFIEvent(ctx context.Context, event *core.FFIEventDefinition) error {
+func (cm *contractManager) validateFFIEvent(ctx context.Context, event *fftypes.FFIEventDefinition) error {
 	if event.Name == "" {
 		return i18n.NewError(ctx, coremsgs.MsgEventNameMustBeSet)
 	}
@@ -445,7 +445,7 @@ func (cm *contractManager) validateInvokeContractRequest(ctx context.Context, re
 	return nil
 }
 
-func (cm *contractManager) resolveEvent(ctx context.Context, ffi *core.FFIReference, eventPath string) (*core.FFISerializedEvent, error) {
+func (cm *contractManager) resolveEvent(ctx context.Context, ffi *fftypes.FFIReference, eventPath string) (*core.FFISerializedEvent, error) {
 	if err := cm.resolveFFIReference(ctx, ffi); err != nil {
 		return nil, err
 	}
@@ -463,11 +463,11 @@ func (cm *contractManager) AddContractListener(ctx context.Context, listener *co
 	listener.Namespace = cm.namespace
 
 	if listener.Name != "" {
-		if err := core.ValidateFFNameField(ctx, listener.Name, "name"); err != nil {
+		if err := fftypes.ValidateFFNameField(ctx, listener.Name, "name"); err != nil {
 			return nil, err
 		}
 	}
-	if err := core.ValidateFFNameField(ctx, listener.Topic, "topic"); err != nil {
+	if err := fftypes.ValidateFFNameField(ctx, listener.Topic, "topic"); err != nil {
 		return nil, err
 	}
 	if listener.Location, err = cm.blockchain.NormalizeContractLocation(ctx, listener.Location); err != nil {
@@ -545,7 +545,7 @@ func (cm *contractManager) AddContractAPIListener(ctx context.Context, apiName,
 	}
 
 	input := &core.ContractListenerInput{ContractListener: *listener}
-	input.Interface = &core.FFIReference{ID: api.Interface.ID}
+	input.Interface = &fftypes.FFIReference{ID: api.Interface.ID}
 	input.EventPath = eventPath
 	if api.Location != nil {
 		input.Location = api.Location
@@ -557,7 +557,7 @@ func (cm *contractManager) AddContractAPIListener(ctx context.Context, apiName,
 func (cm *contractManager) GetContractListenerByNameOrID(ctx context.Context, nameOrID string) (listener *core.ContractListener, err error) {
 	id, err := fftypes.ParseUUID(ctx, nameOrID)
 	if err != nil {
-		if err := core.ValidateFFNameField(ctx, nameOrID, "name"); err != nil {
+		if err := fftypes.ValidateFFNameField(ctx, nameOrID, "name"); err != nil {
 			return nil, err
 		}
 		if listener, err = cm.database.GetContractListener(ctx, cm.namespace, nameOrID); err != nil {
@@ -614,7 +614,7 @@ func (cm *contractManager) DeleteContractListenerByNameOrID(ctx context.Context,
 	})
 }
 
-func (cm *contractManager) checkParamSchema(ctx context.Context, input interface{}, param *core.FFIParam) error {
+func (cm *contractManager) checkParamSchema(ctx context.Context, input interface{}, param *fftypes.FFIParam) error {
 	// TODO: Cache the compiled schema?
 	c := jsonschema.NewCompiler()
 	err := c.AddResource(param.Name, strings.NewReader(param.Schema.String()))
@@ -631,7 +631,7 @@ func (cm *contractManager) checkParamSchema(ctx context.Context, input interface
 	return nil
 }
 
-func (cm *contractManager) GenerateFFI(ctx context.Context, generationRequest *core.FFIGenerationRequest) (*core.FFI, error) {
+func (cm *contractManager) GenerateFFI(ctx context.Context, generationRequest *fftypes.FFIGenerationRequest) (*fftypes.FFI, error) {
 	generationRequest.Namespace = cm.namespace
 	return cm.blockchain.GenerateFFI(ctx, generationRequest)
 }
diff --git a/internal/coremsgs/en_api_translations.go b/internal/coremsgs/en_api_translations.go
index df32b0580..d3149d2ec 100644
--- a/internal/coremsgs/en_api_translations.go
+++ b/internal/coremsgs/en_api_translations.go
@@ -189,7 +189,6 @@ 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")
diff --git a/internal/database/sqlcommon/contractapis_sql.go b/internal/database/sqlcommon/contractapis_sql.go
index 51ce704c0..3f7609f9a 100644
--- a/internal/database/sqlcommon/contractapis_sql.go
+++ b/internal/database/sqlcommon/contractapis_sql.go
@@ -114,7 +114,7 @@ func (s *SQLCommon) UpsertContractAPI(ctx context.Context, api *core.ContractAPI
 
 func (s *SQLCommon) contractAPIResult(ctx context.Context, row *sql.Rows) (*core.ContractAPI, error) {
 	api := core.ContractAPI{
-		Interface: &core.FFIReference{},
+		Interface: &fftypes.FFIReference{},
 	}
 	err := row.Scan(
 		&api.ID,
diff --git a/internal/database/sqlcommon/contractlisteners_sql.go b/internal/database/sqlcommon/contractlisteners_sql.go
index 3acbb97e5..6844692a3 100644
--- a/internal/database/sqlcommon/contractlisteners_sql.go
+++ b/internal/database/sqlcommon/contractlisteners_sql.go
@@ -93,7 +93,7 @@ func (s *SQLCommon) InsertContractListener(ctx context.Context, listener *core.C
 
 func (s *SQLCommon) contractListenerResult(ctx context.Context, row *sql.Rows) (*core.ContractListener, error) {
 	listener := core.ContractListener{
-		Interface: &core.FFIReference{},
+		Interface: &fftypes.FFIReference{},
 	}
 	err := row.Scan(
 		&listener.ID,
diff --git a/internal/database/sqlcommon/ffi_events_sql.go b/internal/database/sqlcommon/ffi_events_sql.go
index c9c80747f..2eeb3a6e8 100644
--- a/internal/database/sqlcommon/ffi_events_sql.go
+++ b/internal/database/sqlcommon/ffi_events_sql.go
@@ -47,7 +47,7 @@ var (
 
 const ffieventsTable = "ffievents"
 
-func (s *SQLCommon) UpsertFFIEvent(ctx context.Context, event *core.FFIEvent) (err error) {
+func (s *SQLCommon) UpsertFFIEvent(ctx context.Context, event *fftypes.FFIEvent) (err error) {
 	ctx, tx, autoCommit, err := s.beginOrUseTx(ctx)
 	if err != nil {
 		return err
@@ -101,8 +101,8 @@ func (s *SQLCommon) UpsertFFIEvent(ctx context.Context, event *core.FFIEvent) (e
 	return s.commitTx(ctx, tx, autoCommit)
 }
 
-func (s *SQLCommon) ffiEventResult(ctx context.Context, row *sql.Rows) (*core.FFIEvent, error) {
-	event := core.FFIEvent{}
+func (s *SQLCommon) ffiEventResult(ctx context.Context, row *sql.Rows) (*fftypes.FFIEvent, error) {
+	event := fftypes.FFIEvent{}
 	err := row.Scan(
 		&event.ID,
 		&event.Interface,
@@ -119,7 +119,7 @@ func (s *SQLCommon) ffiEventResult(ctx context.Context, row *sql.Rows) (*core.FF
 	return &event, nil
 }
 
-func (s *SQLCommon) getFFIEventPred(ctx context.Context, desc string, pred interface{}) (*core.FFIEvent, error) {
+func (s *SQLCommon) getFFIEventPred(ctx context.Context, desc string, pred interface{}) (*fftypes.FFIEvent, error) {
 	rows, _, err := s.query(ctx, ffieventsTable,
 		sq.Select(ffiEventsColumns...).
 			From(ffieventsTable).
@@ -143,7 +143,7 @@ func (s *SQLCommon) getFFIEventPred(ctx context.Context, desc string, pred inter
 	return ci, nil
 }
 
-func (s *SQLCommon) GetFFIEvents(ctx context.Context, namespace string, filter database.Filter) (events []*core.FFIEvent, res *database.FilterResult, err error) {
+func (s *SQLCommon) GetFFIEvents(ctx context.Context, namespace string, filter database.Filter) (events []*fftypes.FFIEvent, res *database.FilterResult, err error) {
 	query, fop, fi, err := s.filterSelect(ctx, "", sq.Select(ffiEventsColumns...).From(ffieventsTable),
 		filter, ffiEventFilterFieldMap, []interface{}{"sequence"}, sq.Eq{"namespace": namespace})
 	if err != nil {
@@ -168,6 +168,6 @@ func (s *SQLCommon) GetFFIEvents(ctx context.Context, namespace string, filter d
 
 }
 
-func (s *SQLCommon) GetFFIEvent(ctx context.Context, namespace string, interfaceID *fftypes.UUID, pathName string) (*core.FFIEvent, error) {
+func (s *SQLCommon) GetFFIEvent(ctx context.Context, namespace string, interfaceID *fftypes.UUID, pathName string) (*fftypes.FFIEvent, error) {
 	return s.getFFIEventPred(ctx, namespace+":"+pathName, sq.Eq{"namespace": namespace, "interface_id": interfaceID, "pathname": pathName})
 }
diff --git a/internal/database/sqlcommon/ffi_methods_sql.go b/internal/database/sqlcommon/ffi_methods_sql.go
index 1851c3e44..4c341116f 100644
--- a/internal/database/sqlcommon/ffi_methods_sql.go
+++ b/internal/database/sqlcommon/ffi_methods_sql.go
@@ -48,7 +48,7 @@ var (
 
 const ffimethodsTable = "ffimethods"
 
-func (s *SQLCommon) UpsertFFIMethod(ctx context.Context, method *core.FFIMethod) (err error) {
+func (s *SQLCommon) UpsertFFIMethod(ctx context.Context, method *fftypes.FFIMethod) (err error) {
 	ctx, tx, autoCommit, err := s.beginOrUseTx(ctx)
 	if err != nil {
 		return err
@@ -104,8 +104,8 @@ func (s *SQLCommon) UpsertFFIMethod(ctx context.Context, method *core.FFIMethod)
 	return s.commitTx(ctx, tx, autoCommit)
 }
 
-func (s *SQLCommon) ffiMethodResult(ctx context.Context, row *sql.Rows) (*core.FFIMethod, error) {
-	method := core.FFIMethod{}
+func (s *SQLCommon) ffiMethodResult(ctx context.Context, row *sql.Rows) (*fftypes.FFIMethod, error) {
+	method := fftypes.FFIMethod{}
 	err := row.Scan(
 		&method.ID,
 		&method.Interface,
@@ -123,7 +123,7 @@ func (s *SQLCommon) ffiMethodResult(ctx context.Context, row *sql.Rows) (*core.F
 	return &method, nil
 }
 
-func (s *SQLCommon) getFFIMethodPred(ctx context.Context, desc string, pred interface{}) (*core.FFIMethod, error) {
+func (s *SQLCommon) getFFIMethodPred(ctx context.Context, desc string, pred interface{}) (*fftypes.FFIMethod, error) {
 	rows, _, err := s.query(ctx, ffimethodsTable,
 		sq.Select(ffiMethodsColumns...).
 			From(ffimethodsTable).
@@ -147,7 +147,7 @@ func (s *SQLCommon) getFFIMethodPred(ctx context.Context, desc string, pred inte
 	return ci, nil
 }
 
-func (s *SQLCommon) GetFFIMethods(ctx context.Context, namespace string, filter database.Filter) (methods []*core.FFIMethod, res *database.FilterResult, err error) {
+func (s *SQLCommon) GetFFIMethods(ctx context.Context, namespace string, filter database.Filter) (methods []*fftypes.FFIMethod, res *database.FilterResult, err error) {
 	query, fop, fi, err := s.filterSelect(ctx, "", sq.Select(ffiMethodsColumns...).From(ffimethodsTable),
 		filter, ffiMethodFilterFieldMap, []interface{}{"sequence"}, sq.Eq{"namespace": namespace})
 	if err != nil {
@@ -172,6 +172,6 @@ func (s *SQLCommon) GetFFIMethods(ctx context.Context, namespace string, filter
 
 }
 
-func (s *SQLCommon) GetFFIMethod(ctx context.Context, ns string, interfaceID *fftypes.UUID, pathName string) (*core.FFIMethod, error) {
+func (s *SQLCommon) GetFFIMethod(ctx context.Context, ns string, interfaceID *fftypes.UUID, pathName string) (*fftypes.FFIMethod, error) {
 	return s.getFFIMethodPred(ctx, ns+":"+pathName, sq.Eq{"namespace": ns, "interface_id": interfaceID, "pathname": pathName})
 }
diff --git a/internal/database/sqlcommon/ffi_sql.go b/internal/database/sqlcommon/ffi_sql.go
index 8d45111ed..d348154f3 100644
--- a/internal/database/sqlcommon/ffi_sql.go
+++ b/internal/database/sqlcommon/ffi_sql.go
@@ -45,7 +45,7 @@ var (
 
 const ffiTable = "ffi"
 
-func (s *SQLCommon) UpsertFFI(ctx context.Context, ffi *core.FFI) (err error) {
+func (s *SQLCommon) UpsertFFI(ctx context.Context, ffi *fftypes.FFI) (err error) {
 	ctx, tx, autoCommit, err := s.beginOrUseTx(ctx)
 	if err != nil {
 		return err
@@ -100,8 +100,8 @@ func (s *SQLCommon) UpsertFFI(ctx context.Context, ffi *core.FFI) (err error) {
 	return s.commitTx(ctx, tx, autoCommit)
 }
 
-func (s *SQLCommon) ffiResult(ctx context.Context, row *sql.Rows) (*core.FFI, error) {
-	ffi := core.FFI{}
+func (s *SQLCommon) ffiResult(ctx context.Context, row *sql.Rows) (*fftypes.FFI, error) {
+	ffi := fftypes.FFI{}
 	err := row.Scan(
 		&ffi.ID,
 		&ffi.Namespace,
@@ -116,7 +116,7 @@ func (s *SQLCommon) ffiResult(ctx context.Context, row *sql.Rows) (*core.FFI, er
 	return &ffi, nil
 }
 
-func (s *SQLCommon) getFFIPred(ctx context.Context, desc string, pred interface{}) (*core.FFI, error) {
+func (s *SQLCommon) getFFIPred(ctx context.Context, desc string, pred interface{}) (*fftypes.FFI, error) {
 	rows, _, err := s.query(ctx, ffiTable,
 		sq.Select(ffiColumns...).
 			From(ffiTable).
@@ -140,7 +140,7 @@ func (s *SQLCommon) getFFIPred(ctx context.Context, desc string, pred interface{
 	return ffi, nil
 }
 
-func (s *SQLCommon) GetFFIs(ctx context.Context, namespace string, filter database.Filter) (ffis []*core.FFI, res *database.FilterResult, err error) {
+func (s *SQLCommon) GetFFIs(ctx context.Context, namespace string, filter database.Filter) (ffis []*fftypes.FFI, res *database.FilterResult, err error) {
 
 	query, fop, fi, err := s.filterSelect(ctx, "", sq.Select(ffiColumns...).From(ffiTable),
 		filter, ffiFilterFieldMap, []interface{}{"sequence"}, sq.Eq{"namespace": namespace})
@@ -154,7 +154,7 @@ func (s *SQLCommon) GetFFIs(ctx context.Context, namespace string, filter databa
 	}
 	defer rows.Close()
 
-	ffis = []*core.FFI{}
+	ffis = []*fftypes.FFI{}
 	for rows.Next() {
 		cd, err := s.ffiResult(ctx, rows)
 		if err != nil {
@@ -167,10 +167,10 @@ func (s *SQLCommon) GetFFIs(ctx context.Context, namespace string, filter databa
 
 }
 
-func (s *SQLCommon) GetFFIByID(ctx context.Context, namespace string, id *fftypes.UUID) (*core.FFI, error) {
+func (s *SQLCommon) GetFFIByID(ctx context.Context, namespace string, id *fftypes.UUID) (*fftypes.FFI, error) {
 	return s.getFFIPred(ctx, id.String(), sq.Eq{"id": id, "namespace": namespace})
 }
 
-func (s *SQLCommon) GetFFI(ctx context.Context, namespace, name, version string) (*core.FFI, error) {
+func (s *SQLCommon) GetFFI(ctx context.Context, namespace, name, version string) (*fftypes.FFI, error) {
 	return s.getFFIPred(ctx, namespace+":"+name+":"+version, sq.Eq{"namespace": namespace, "name": name, "version": version})
 }
diff --git a/internal/definitions/handler_contracts.go b/internal/definitions/handler_contracts.go
index d00de0483..49ebe8860 100644
--- a/internal/definitions/handler_contracts.go
+++ b/internal/definitions/handler_contracts.go
@@ -27,7 +27,7 @@ import (
 	"github.com/hyperledger/firefly/pkg/database"
 )
 
-func (dh *definitionHandler) persistFFI(ctx context.Context, ffi *core.FFI) (retry bool, err error) {
+func (dh *definitionHandler) persistFFI(ctx context.Context, ffi *fftypes.FFI) (retry bool, err error) {
 	if err = dh.contracts.ResolveFFI(ctx, ffi); err != nil {
 		return false, i18n.NewError(ctx, coremsgs.MsgDefRejectedValidateFail, "contract interface", ffi.ID, err)
 	}
@@ -67,7 +67,7 @@ func (dh *definitionHandler) persistContractAPI(ctx context.Context, httpServerU
 }
 
 func (dh *definitionHandler) handleFFIBroadcast(ctx context.Context, state *core.BatchState, msg *core.Message, data core.DataArray, tx *fftypes.UUID) (HandlerResult, error) {
-	var ffi core.FFI
+	var ffi fftypes.FFI
 	if valid := dh.getSystemBroadcastPayload(ctx, msg, data, &ffi); !valid {
 		return HandlerResult{Action: ActionReject}, i18n.NewError(ctx, coremsgs.MsgDefRejectedBadPayload, "contract interface", msg.Header.ID)
 	}
@@ -79,7 +79,7 @@ func (dh *definitionHandler) handleFFIBroadcast(ctx context.Context, state *core
 	return dh.handleFFIDefinition(ctx, state, &ffi, tx)
 }
 
-func (dh *definitionHandler) handleFFIDefinition(ctx context.Context, state *core.BatchState, ffi *core.FFI, tx *fftypes.UUID) (HandlerResult, error) {
+func (dh *definitionHandler) handleFFIDefinition(ctx context.Context, state *core.BatchState, ffi *fftypes.FFI, tx *fftypes.UUID) (HandlerResult, error) {
 	l := log.L(ctx)
 	if retry, err := dh.persistFFI(ctx, ffi); err != nil {
 		if retry {
diff --git a/internal/definitions/sender.go b/internal/definitions/sender.go
index fe85cb0c3..d034293b2 100644
--- a/internal/definitions/sender.go
+++ b/internal/definitions/sender.go
@@ -41,7 +41,7 @@ type Sender interface {
 	UpdateIdentity(ctx context.Context, identity *core.Identity, def *core.IdentityUpdate, signingIdentity *core.SignerRef, waitConfirm bool) error
 	DefineDatatype(ctx context.Context, datatype *core.Datatype, waitConfirm bool) error
 	DefineTokenPool(ctx context.Context, pool *core.TokenPoolAnnouncement, waitConfirm bool) error
-	DefineFFI(ctx context.Context, ffi *core.FFI, waitConfirm bool) error
+	DefineFFI(ctx context.Context, ffi *fftypes.FFI, waitConfirm bool) error
 	DefineContractAPI(ctx context.Context, httpServerURL string, api *core.ContractAPI, waitConfirm bool) error
 }
 
diff --git a/internal/definitions/sender_contracts.go b/internal/definitions/sender_contracts.go
index 59026521b..41b4919a9 100644
--- a/internal/definitions/sender_contracts.go
+++ b/internal/definitions/sender_contracts.go
@@ -23,7 +23,7 @@ import (
 	"github.com/hyperledger/firefly/pkg/core"
 )
 
-func (bm *definitionSender) DefineFFI(ctx context.Context, ffi *core.FFI, waitConfirm bool) error {
+func (bm *definitionSender) DefineFFI(ctx context.Context, ffi *fftypes.FFI, waitConfirm bool) error {
 	ffi.ID = fftypes.NewUUID()
 	ffi.Namespace = bm.namespace
 	for _, method := range ffi.Methods {
diff --git a/internal/events/batch_pin_complete.go b/internal/events/batch_pin_complete.go
index 9f6a5e0e2..914d54281 100644
--- a/internal/events/batch_pin_complete.go
+++ b/internal/events/batch_pin_complete.go
@@ -40,7 +40,8 @@ func (em *eventManager) BatchPinComplete(batchPin *blockchain.BatchPin, signingK
 		log.L(em.ctx).Errorf("Invalid BatchPin transaction - ID is nil")
 		return nil // move on
 	}
-	if err := core.ValidateFFNameField(em.ctx, batchPin.Namespace, "namespace"); err != nil {
+
+	if err := fftypes.ValidateFFNameField(em.ctx, batchPin.Namespace, "namespace"); err != nil {
 		log.L(em.ctx).Errorf("Invalid transaction ID='%s' - invalid namespace '%s': %a", batchPin.TransactionID, batchPin.Namespace, err)
 		return nil // move on
 	}
diff --git a/internal/namespace/manager.go b/internal/namespace/manager.go
index 4fea92bf1..807efa8d0 100644
--- a/internal/namespace/manager.go
+++ b/internal/namespace/manager.go
@@ -347,7 +347,7 @@ func (nm *namespaceManager) getTokensPlugins(ctx context.Context) (plugins map[s
 			if name == "" || pluginType == "" {
 				return nil, i18n.NewError(ctx, coremsgs.MsgMissingTokensPluginConfig)
 			}
-			if err = core.ValidateFFNameField(ctx, name, "name"); err != nil {
+			if err = fftypes.ValidateFFNameField(ctx, name, "name"); err != nil {
 				return nil, err
 			}
 
@@ -415,7 +415,7 @@ func (nm *namespaceManager) validatePluginConfig(ctx context.Context, config con
 		return "", "", i18n.NewError(ctx, coremsgs.MsgInvalidPluginConfiguration, sectionName)
 	}
 
-	if err := core.ValidateFFNameField(ctx, name, "name"); err != nil {
+	if err := fftypes.ValidateFFNameField(ctx, name, "name"); err != nil {
 		return "", "", err
 	}
 
@@ -637,7 +637,7 @@ func (nm *namespaceManager) loadNamespaces(ctx context.Context) (err error) {
 }
 
 func (nm *namespaceManager) loadNamespace(ctx context.Context, name string, index int, conf config.Section) (*namespace, error) {
-	if err := core.ValidateFFNameField(ctx, name, fmt.Sprintf("namespaces.predefined[%d].name", index)); err != nil {
+	if err := fftypes.ValidateFFNameField(ctx, name, fmt.Sprintf("namespaces.predefined[%d].name", index)); err != nil {
 		return nil, err
 	}
 	if name == core.LegacySystemNamespace || conf.GetString(coreconfig.NamespaceRemoteName) == core.LegacySystemNamespace {
diff --git a/internal/networkmap/data_query.go b/internal/networkmap/data_query.go
index 0d86c0e97..fa925634d 100644
--- a/internal/networkmap/data_query.go
+++ b/internal/networkmap/data_query.go
@@ -31,7 +31,7 @@ import (
 func (nm *networkMap) GetOrganizationByNameOrID(ctx context.Context, nameOrID string) (org *core.Identity, err error) {
 	u, err := fftypes.ParseUUID(ctx, nameOrID)
 	if err != nil {
-		if err := core.ValidateFFNameField(ctx, nameOrID, "name"); err != nil {
+		if err := fftypes.ValidateFFNameField(ctx, nameOrID, "name"); err != nil {
 			return nil, err
 		}
 		if org, err = nm.database.GetIdentityByName(ctx, core.IdentityTypeOrg, nm.namespace, nameOrID); err != nil {
@@ -63,7 +63,7 @@ func (nm *networkMap) GetOrganizationsWithVerifiers(ctx context.Context, filter
 func (nm *networkMap) GetNodeByNameOrID(ctx context.Context, nameOrID string) (node *core.Identity, err error) {
 	u, err := fftypes.ParseUUID(ctx, nameOrID)
 	if err != nil {
-		if err := core.ValidateFFNameField(ctx, nameOrID, "name"); err != nil {
+		if err := fftypes.ValidateFFNameField(ctx, nameOrID, "name"); err != nil {
 			return nil, err
 		}
 		if node, err = nm.database.GetIdentityByName(ctx, core.IdentityTypeNode, nm.namespace, nameOrID); err != nil {
diff --git a/internal/orchestrator/data_query.go b/internal/orchestrator/data_query.go
index bb0c83daf..6f144cf13 100644
--- a/internal/orchestrator/data_query.go
+++ b/internal/orchestrator/data_query.go
@@ -113,7 +113,7 @@ func (or *orchestrator) GetDatatypeByID(ctx context.Context, id string) (*core.D
 }
 
 func (or *orchestrator) GetDatatypeByName(ctx context.Context, name, version string) (*core.Datatype, error) {
-	if err := core.ValidateFFNameFieldNoUUID(ctx, name, "name"); err != nil {
+	if err := fftypes.ValidateFFNameFieldNoUUID(ctx, name, "name"); err != nil {
 		return nil, err
 	}
 	return or.database().GetDatatypeByName(ctx, or.namespace, name, version)
diff --git a/internal/orchestrator/subscriptions.go b/internal/orchestrator/subscriptions.go
index 2d390fc81..2b2313ff2 100644
--- a/internal/orchestrator/subscriptions.go
+++ b/internal/orchestrator/subscriptions.go
@@ -40,7 +40,7 @@ func (or *orchestrator) createUpdateSubscription(ctx context.Context, subDef *co
 	subDef.Created = fftypes.Now()
 	subDef.Namespace = or.namespace
 	subDef.Ephemeral = false
-	if err := core.ValidateFFNameFieldNoUUID(ctx, subDef.Name, "name"); err != nil {
+	if err := fftypes.ValidateFFNameFieldNoUUID(ctx, subDef.Name, "name"); err != nil {
 		return nil, err
 	}
 	if subDef.Transport == system.SystemEventsTransport {
diff --git a/internal/reference/reference.go b/internal/reference/reference.go
index d53cc4a6a..6bb44a02d 100644
--- a/internal/reference/reference.go
+++ b/internal/reference/reference.go
@@ -92,7 +92,7 @@ func GenerateObjectsReferenceMarkdown(ctx context.Context) (map[string][]byte, e
 			ID:        fftypes.MustParseUUID("0f12317b-85a0-4a77-a722-857ea2b0a5fa"),
 			Name:      "my_contract_api",
 			Namespace: "ns1",
-			Interface: &core.FFIReference{
+			Interface: &fftypes.FFIReference{
 				ID: fftypes.MustParseUUID("c35d3449-4f24-4676-8e64-91c9e46f06c4"),
 			},
 			Location: fftypes.JSONAnyPtr(`{
@@ -157,14 +157,14 @@ func GenerateObjectsReferenceMarkdown(ctx context.Context) (map[string][]byte, e
 			Created: fftypes.UnixTime(1652664195),
 		},
 
-		&core.FFI{
+		&fftypes.FFI{
 			ID:          fftypes.MustParseUUID("c35d3449-4f24-4676-8e64-91c9e46f06c4"),
 			Namespace:   "ns1",
 			Name:        "SimpleStorage",
 			Description: "A simple example contract in Solidity",
 			Version:     "v0.0.1",
 			Message:     fftypes.MustParseUUID("e4ad2077-5714-416e-81f9-7964a6223b6f"),
-			Methods: []*core.FFIMethod{
+			Methods: []*fftypes.FFIMethod{
 				{
 					ID:          fftypes.MustParseUUID("8f3289dd-3a19-4a9f-aab3-cb05289b013c"),
 					Interface:   fftypes.MustParseUUID("c35d3449-4f24-4676-8e64-91c9e46f06c4"),
@@ -172,8 +172,8 @@ func GenerateObjectsReferenceMarkdown(ctx context.Context) (map[string][]byte, e
 					Namespace:   "ns1",
 					Pathname:    "get",
 					Description: "Get the current value",
-					Params:      core.FFIParams{},
-					Returns: core.FFIParams{
+					Params:      fftypes.FFIParams{},
+					Returns: fftypes.FFIParams{
 						{
 							Name: "output",
 							Schema: fftypes.JSONAnyPtr(`{
@@ -195,7 +195,7 @@ func GenerateObjectsReferenceMarkdown(ctx context.Context) (map[string][]byte, e
 					Namespace:   "ns1",
 					Pathname:    "set",
 					Description: "Set the value",
-					Params: core.FFIParams{
+					Params: fftypes.FFIParams{
 						{
 							Name: "newValue",
 							Schema: fftypes.JSONAnyPtr(`{
@@ -206,23 +206,23 @@ func GenerateObjectsReferenceMarkdown(ctx context.Context) (map[string][]byte, e
 							}`),
 						},
 					},
-					Returns: core.FFIParams{},
+					Returns: fftypes.FFIParams{},
 					Details: fftypes.JSONObject{
 						"stateMutability": "payable",
 					},
 				},
 			},
-			Events: []*core.FFIEvent{
+			Events: []*fftypes.FFIEvent{
 				{
 					ID:        fftypes.MustParseUUID("9f653f93-86f4-45bc-be75-d7f5888fbbc0"),
 					Interface: fftypes.MustParseUUID("c35d3449-4f24-4676-8e64-91c9e46f06c4"),
 					Namespace: "ns1",
 					Pathname:  "Changed",
 					Signature: "Changed(address,uint256)",
-					FFIEventDefinition: core.FFIEventDefinition{
+					FFIEventDefinition: fftypes.FFIEventDefinition{
 						Name:        "Changed",
 						Description: "Emitted when the value changes",
-						Params: core.FFIParams{
+						Params: fftypes.FFIParams{
 							{
 								Name: "_from",
 								Schema: fftypes.JSONAnyPtr(`{
@@ -251,7 +251,7 @@ func GenerateObjectsReferenceMarkdown(ctx context.Context) (map[string][]byte, e
 
 		&core.ContractListener{
 			ID: fftypes.MustParseUUID("d61980a9-748c-4c72-baf5-8b485b514d59"),
-			Interface: &core.FFIReference{
+			Interface: &fftypes.FFIReference{
 				ID: fftypes.MustParseUUID("ff1da3c1-f9e7-40c2-8d93-abb8855e8a1d"),
 			},
 			Namespace: "ns1",
@@ -262,9 +262,9 @@ func GenerateObjectsReferenceMarkdown(ctx context.Context) (map[string][]byte, e
 			}`),
 			Created: fftypes.UnixTime(1652664195),
 			Event: &core.FFISerializedEvent{
-				FFIEventDefinition: core.FFIEventDefinition{
+				FFIEventDefinition: fftypes.FFIEventDefinition{
 					Name: "Changed",
-					Params: core.FFIParams{
+					Params: fftypes.FFIParams{
 						{
 							Name: "x",
 							Schema: fftypes.JSONAnyPtr(`{
diff --git a/mocks/apiservermocks/ffi_swagger_gen.go b/mocks/apiservermocks/ffi_swagger_gen.go
index b2627ad06..ef5859a0b 100644
--- a/mocks/apiservermocks/ffi_swagger_gen.go
+++ b/mocks/apiservermocks/ffi_swagger_gen.go
@@ -5,7 +5,9 @@ package apiservermocks
 import (
 	context "context"
 
+	fftypes "github.com/hyperledger/firefly-common/pkg/fftypes"
 	core "github.com/hyperledger/firefly/pkg/core"
+
 	mock "github.com/stretchr/testify/mock"
 
 	openapi3 "github.com/getkin/kin-openapi/openapi3"
@@ -17,11 +19,11 @@ type FFISwaggerGen struct {
 }
 
 // Generate provides a mock function with given fields: ctx, baseURL, api, ffi
-func (_m *FFISwaggerGen) Generate(ctx context.Context, baseURL string, api *core.ContractAPI, ffi *core.FFI) *openapi3.T {
+func (_m *FFISwaggerGen) Generate(ctx context.Context, baseURL string, api *core.ContractAPI, ffi *fftypes.FFI) *openapi3.T {
 	ret := _m.Called(ctx, baseURL, api, ffi)
 
 	var r0 *openapi3.T
-	if rf, ok := ret.Get(0).(func(context.Context, string, *core.ContractAPI, *core.FFI) *openapi3.T); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, string, *core.ContractAPI, *fftypes.FFI) *openapi3.T); ok {
 		r0 = rf(ctx, baseURL, api, ffi)
 	} else {
 		if ret.Get(0) != nil {
diff --git a/mocks/blockchainmocks/plugin.go b/mocks/blockchainmocks/plugin.go
index ca4c4ae70..f2ee50020 100644
--- a/mocks/blockchainmocks/plugin.go
+++ b/mocks/blockchainmocks/plugin.go
@@ -88,11 +88,11 @@ func (_m *Plugin) DeleteContractListener(ctx context.Context, subscription *core
 }
 
 // GenerateEventSignature provides a mock function with given fields: ctx, event
-func (_m *Plugin) GenerateEventSignature(ctx context.Context, event *core.FFIEventDefinition) string {
+func (_m *Plugin) GenerateEventSignature(ctx context.Context, event *fftypes.FFIEventDefinition) string {
 	ret := _m.Called(ctx, event)
 
 	var r0 string
-	if rf, ok := ret.Get(0).(func(context.Context, *core.FFIEventDefinition) string); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *fftypes.FFIEventDefinition) string); ok {
 		r0 = rf(ctx, event)
 	} else {
 		r0 = ret.Get(0).(string)
@@ -102,20 +102,20 @@ func (_m *Plugin) GenerateEventSignature(ctx context.Context, event *core.FFIEve
 }
 
 // GenerateFFI provides a mock function with given fields: ctx, generationRequest
-func (_m *Plugin) GenerateFFI(ctx context.Context, generationRequest *core.FFIGenerationRequest) (*core.FFI, error) {
+func (_m *Plugin) GenerateFFI(ctx context.Context, generationRequest *fftypes.FFIGenerationRequest) (*fftypes.FFI, error) {
 	ret := _m.Called(ctx, generationRequest)
 
-	var r0 *core.FFI
-	if rf, ok := ret.Get(0).(func(context.Context, *core.FFIGenerationRequest) *core.FFI); ok {
+	var r0 *fftypes.FFI
+	if rf, ok := ret.Get(0).(func(context.Context, *fftypes.FFIGenerationRequest) *fftypes.FFI); ok {
 		r0 = rf(ctx, generationRequest)
 	} else {
 		if ret.Get(0) != nil {
-			r0 = ret.Get(0).(*core.FFI)
+			r0 = ret.Get(0).(*fftypes.FFI)
 		}
 	}
 
 	var r1 error
-	if rf, ok := ret.Get(1).(func(context.Context, *core.FFIGenerationRequest) error); ok {
+	if rf, ok := ret.Get(1).(func(context.Context, *fftypes.FFIGenerationRequest) error); ok {
 		r1 = rf(ctx, generationRequest)
 	} else {
 		r1 = ret.Error(1)
@@ -155,15 +155,15 @@ func (_m *Plugin) GetAndConvertDeprecatedContractConfig(ctx context.Context) (*f
 }
 
 // GetFFIParamValidator provides a mock function with given fields: ctx
-func (_m *Plugin) GetFFIParamValidator(ctx context.Context) (core.FFIParamValidator, error) {
+func (_m *Plugin) GetFFIParamValidator(ctx context.Context) (fftypes.FFIParamValidator, error) {
 	ret := _m.Called(ctx)
 
-	var r0 core.FFIParamValidator
-	if rf, ok := ret.Get(0).(func(context.Context) core.FFIParamValidator); ok {
+	var r0 fftypes.FFIParamValidator
+	if rf, ok := ret.Get(0).(func(context.Context) fftypes.FFIParamValidator); ok {
 		r0 = rf(ctx)
 	} else {
 		if ret.Get(0) != nil {
-			r0 = ret.Get(0).(core.FFIParamValidator)
+			r0 = ret.Get(0).(fftypes.FFIParamValidator)
 		}
 	}
 
@@ -218,11 +218,11 @@ func (_m *Plugin) InitConfig(_a0 config.Section) {
 }
 
 // InvokeContract provides a mock function with given fields: ctx, nsOpID, signingKey, location, method, input, options
-func (_m *Plugin) InvokeContract(ctx context.Context, nsOpID string, signingKey string, location *fftypes.JSONAny, method *core.FFIMethod, input map[string]interface{}, options map[string]interface{}) error {
+func (_m *Plugin) InvokeContract(ctx context.Context, nsOpID string, signingKey string, location *fftypes.JSONAny, method *fftypes.FFIMethod, input map[string]interface{}, options map[string]interface{}) error {
 	ret := _m.Called(ctx, nsOpID, signingKey, location, method, input, options)
 
 	var r0 error
-	if rf, ok := ret.Get(0).(func(context.Context, string, string, *fftypes.JSONAny, *core.FFIMethod, map[string]interface{}, map[string]interface{}) error); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, string, string, *fftypes.JSONAny, *fftypes.FFIMethod, map[string]interface{}, map[string]interface{}) error); ok {
 		r0 = rf(ctx, nsOpID, signingKey, location, method, input, options)
 	} else {
 		r0 = ret.Error(0)
@@ -290,11 +290,11 @@ func (_m *Plugin) NormalizeSigningKey(ctx context.Context, keyRef string) (strin
 }
 
 // QueryContract provides a mock function with given fields: ctx, location, method, input, options
-func (_m *Plugin) QueryContract(ctx context.Context, location *fftypes.JSONAny, method *core.FFIMethod, input map[string]interface{}, options map[string]interface{}) (interface{}, error) {
+func (_m *Plugin) QueryContract(ctx context.Context, location *fftypes.JSONAny, method *fftypes.FFIMethod, input map[string]interface{}, options map[string]interface{}) (interface{}, error) {
 	ret := _m.Called(ctx, location, method, input, options)
 
 	var r0 interface{}
-	if rf, ok := ret.Get(0).(func(context.Context, *fftypes.JSONAny, *core.FFIMethod, map[string]interface{}, map[string]interface{}) interface{}); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *fftypes.JSONAny, *fftypes.FFIMethod, map[string]interface{}, map[string]interface{}) interface{}); ok {
 		r0 = rf(ctx, location, method, input, options)
 	} else {
 		if ret.Get(0) != nil {
@@ -303,7 +303,7 @@ func (_m *Plugin) QueryContract(ctx context.Context, location *fftypes.JSONAny,
 	}
 
 	var r1 error
-	if rf, ok := ret.Get(1).(func(context.Context, *fftypes.JSONAny, *core.FFIMethod, map[string]interface{}, map[string]interface{}) error); ok {
+	if rf, ok := ret.Get(1).(func(context.Context, *fftypes.JSONAny, *fftypes.FFIMethod, map[string]interface{}, map[string]interface{}) error); ok {
 		r1 = rf(ctx, location, method, input, options)
 	} else {
 		r1 = ret.Error(1)
diff --git a/mocks/contractmocks/manager.go b/mocks/contractmocks/manager.go
index bdc6171ba..1924f395f 100644
--- a/mocks/contractmocks/manager.go
+++ b/mocks/contractmocks/manager.go
@@ -80,20 +80,20 @@ func (_m *Manager) DeleteContractListenerByNameOrID(ctx context.Context, nameOrI
 }
 
 // GenerateFFI provides a mock function with given fields: ctx, generationRequest
-func (_m *Manager) GenerateFFI(ctx context.Context, generationRequest *core.FFIGenerationRequest) (*core.FFI, error) {
+func (_m *Manager) GenerateFFI(ctx context.Context, generationRequest *fftypes.FFIGenerationRequest) (*fftypes.FFI, error) {
 	ret := _m.Called(ctx, generationRequest)
 
-	var r0 *core.FFI
-	if rf, ok := ret.Get(0).(func(context.Context, *core.FFIGenerationRequest) *core.FFI); ok {
+	var r0 *fftypes.FFI
+	if rf, ok := ret.Get(0).(func(context.Context, *fftypes.FFIGenerationRequest) *fftypes.FFI); ok {
 		r0 = rf(ctx, generationRequest)
 	} else {
 		if ret.Get(0) != nil {
-			r0 = ret.Get(0).(*core.FFI)
+			r0 = ret.Get(0).(*fftypes.FFI)
 		}
 	}
 
 	var r1 error
-	if rf, ok := ret.Get(1).(func(context.Context, *core.FFIGenerationRequest) error); ok {
+	if rf, ok := ret.Get(1).(func(context.Context, *fftypes.FFIGenerationRequest) error); ok {
 		r1 = rf(ctx, generationRequest)
 	} else {
 		r1 = ret.Error(1)
@@ -126,15 +126,15 @@ func (_m *Manager) GetContractAPI(ctx context.Context, httpServerURL string, api
 }
 
 // GetContractAPIInterface provides a mock function with given fields: ctx, apiName
-func (_m *Manager) GetContractAPIInterface(ctx context.Context, apiName string) (*core.FFI, error) {
+func (_m *Manager) GetContractAPIInterface(ctx context.Context, apiName string) (*fftypes.FFI, error) {
 	ret := _m.Called(ctx, apiName)
 
-	var r0 *core.FFI
-	if rf, ok := ret.Get(0).(func(context.Context, string) *core.FFI); ok {
+	var r0 *fftypes.FFI
+	if rf, ok := ret.Get(0).(func(context.Context, string) *fftypes.FFI); ok {
 		r0 = rf(ctx, apiName)
 	} else {
 		if ret.Get(0) != nil {
-			r0 = ret.Get(0).(*core.FFI)
+			r0 = ret.Get(0).(*fftypes.FFI)
 		}
 	}
 
@@ -268,15 +268,15 @@ func (_m *Manager) GetContractListeners(ctx context.Context, filter database.And
 }
 
 // GetFFI provides a mock function with given fields: ctx, name, version
-func (_m *Manager) GetFFI(ctx context.Context, name string, version string) (*core.FFI, error) {
+func (_m *Manager) GetFFI(ctx context.Context, name string, version string) (*fftypes.FFI, error) {
 	ret := _m.Called(ctx, name, version)
 
-	var r0 *core.FFI
-	if rf, ok := ret.Get(0).(func(context.Context, string, string) *core.FFI); ok {
+	var r0 *fftypes.FFI
+	if rf, ok := ret.Get(0).(func(context.Context, string, string) *fftypes.FFI); ok {
 		r0 = rf(ctx, name, version)
 	} else {
 		if ret.Get(0) != nil {
-			r0 = ret.Get(0).(*core.FFI)
+			r0 = ret.Get(0).(*fftypes.FFI)
 		}
 	}
 
@@ -291,15 +291,15 @@ func (_m *Manager) GetFFI(ctx context.Context, name string, version string) (*co
 }
 
 // GetFFIByID provides a mock function with given fields: ctx, id
-func (_m *Manager) GetFFIByID(ctx context.Context, id *fftypes.UUID) (*core.FFI, error) {
+func (_m *Manager) GetFFIByID(ctx context.Context, id *fftypes.UUID) (*fftypes.FFI, error) {
 	ret := _m.Called(ctx, id)
 
-	var r0 *core.FFI
-	if rf, ok := ret.Get(0).(func(context.Context, *fftypes.UUID) *core.FFI); ok {
+	var r0 *fftypes.FFI
+	if rf, ok := ret.Get(0).(func(context.Context, *fftypes.UUID) *fftypes.FFI); ok {
 		r0 = rf(ctx, id)
 	} else {
 		if ret.Get(0) != nil {
-			r0 = ret.Get(0).(*core.FFI)
+			r0 = ret.Get(0).(*fftypes.FFI)
 		}
 	}
 
@@ -314,15 +314,15 @@ func (_m *Manager) GetFFIByID(ctx context.Context, id *fftypes.UUID) (*core.FFI,
 }
 
 // GetFFIByIDWithChildren provides a mock function with given fields: ctx, id
-func (_m *Manager) GetFFIByIDWithChildren(ctx context.Context, id *fftypes.UUID) (*core.FFI, error) {
+func (_m *Manager) GetFFIByIDWithChildren(ctx context.Context, id *fftypes.UUID) (*fftypes.FFI, error) {
 	ret := _m.Called(ctx, id)
 
-	var r0 *core.FFI
-	if rf, ok := ret.Get(0).(func(context.Context, *fftypes.UUID) *core.FFI); ok {
+	var r0 *fftypes.FFI
+	if rf, ok := ret.Get(0).(func(context.Context, *fftypes.UUID) *fftypes.FFI); ok {
 		r0 = rf(ctx, id)
 	} else {
 		if ret.Get(0) != nil {
-			r0 = ret.Get(0).(*core.FFI)
+			r0 = ret.Get(0).(*fftypes.FFI)
 		}
 	}
 
@@ -337,15 +337,15 @@ func (_m *Manager) GetFFIByIDWithChildren(ctx context.Context, id *fftypes.UUID)
 }
 
 // GetFFIWithChildren provides a mock function with given fields: ctx, name, version
-func (_m *Manager) GetFFIWithChildren(ctx context.Context, name string, version string) (*core.FFI, error) {
+func (_m *Manager) GetFFIWithChildren(ctx context.Context, name string, version string) (*fftypes.FFI, error) {
 	ret := _m.Called(ctx, name, version)
 
-	var r0 *core.FFI
-	if rf, ok := ret.Get(0).(func(context.Context, string, string) *core.FFI); ok {
+	var r0 *fftypes.FFI
+	if rf, ok := ret.Get(0).(func(context.Context, string, string) *fftypes.FFI); ok {
 		r0 = rf(ctx, name, version)
 	} else {
 		if ret.Get(0) != nil {
-			r0 = ret.Get(0).(*core.FFI)
+			r0 = ret.Get(0).(*fftypes.FFI)
 		}
 	}
 
@@ -360,15 +360,15 @@ func (_m *Manager) GetFFIWithChildren(ctx context.Context, name string, version
 }
 
 // GetFFIs provides a mock function with given fields: ctx, filter
-func (_m *Manager) GetFFIs(ctx context.Context, filter database.AndFilter) ([]*core.FFI, *database.FilterResult, error) {
+func (_m *Manager) GetFFIs(ctx context.Context, filter database.AndFilter) ([]*fftypes.FFI, *database.FilterResult, error) {
 	ret := _m.Called(ctx, filter)
 
-	var r0 []*core.FFI
-	if rf, ok := ret.Get(0).(func(context.Context, database.AndFilter) []*core.FFI); ok {
+	var r0 []*fftypes.FFI
+	if rf, ok := ret.Get(0).(func(context.Context, database.AndFilter) []*fftypes.FFI); ok {
 		r0 = rf(ctx, filter)
 	} else {
 		if ret.Get(0) != nil {
-			r0 = ret.Get(0).([]*core.FFI)
+			r0 = ret.Get(0).([]*fftypes.FFI)
 		}
 	}
 
@@ -489,11 +489,11 @@ func (_m *Manager) ResolveContractAPI(ctx context.Context, httpServerURL string,
 }
 
 // ResolveFFI provides a mock function with given fields: ctx, ffi
-func (_m *Manager) ResolveFFI(ctx context.Context, ffi *core.FFI) error {
+func (_m *Manager) ResolveFFI(ctx context.Context, ffi *fftypes.FFI) error {
 	ret := _m.Called(ctx, ffi)
 
 	var r0 error
-	if rf, ok := ret.Get(0).(func(context.Context, *core.FFI) error); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *fftypes.FFI) error); ok {
 		r0 = rf(ctx, ffi)
 	} else {
 		r0 = ret.Error(0)
diff --git a/mocks/databasemocks/plugin.go b/mocks/databasemocks/plugin.go
index 5d2f0eaf4..6bb698418 100644
--- a/mocks/databasemocks/plugin.go
+++ b/mocks/databasemocks/plugin.go
@@ -792,15 +792,15 @@ func (_m *Plugin) GetEvents(ctx context.Context, namespace string, filter databa
 }
 
 // GetFFI provides a mock function with given fields: ctx, namespace, name, version
-func (_m *Plugin) GetFFI(ctx context.Context, namespace string, name string, version string) (*core.FFI, error) {
+func (_m *Plugin) GetFFI(ctx context.Context, namespace string, name string, version string) (*fftypes.FFI, error) {
 	ret := _m.Called(ctx, namespace, name, version)
 
-	var r0 *core.FFI
-	if rf, ok := ret.Get(0).(func(context.Context, string, string, string) *core.FFI); ok {
+	var r0 *fftypes.FFI
+	if rf, ok := ret.Get(0).(func(context.Context, string, string, string) *fftypes.FFI); ok {
 		r0 = rf(ctx, namespace, name, version)
 	} else {
 		if ret.Get(0) != nil {
-			r0 = ret.Get(0).(*core.FFI)
+			r0 = ret.Get(0).(*fftypes.FFI)
 		}
 	}
 
@@ -815,15 +815,15 @@ func (_m *Plugin) GetFFI(ctx context.Context, namespace string, name string, ver
 }
 
 // GetFFIByID provides a mock function with given fields: ctx, namespace, id
-func (_m *Plugin) GetFFIByID(ctx context.Context, namespace string, id *fftypes.UUID) (*core.FFI, error) {
+func (_m *Plugin) GetFFIByID(ctx context.Context, namespace string, id *fftypes.UUID) (*fftypes.FFI, error) {
 	ret := _m.Called(ctx, namespace, id)
 
-	var r0 *core.FFI
-	if rf, ok := ret.Get(0).(func(context.Context, string, *fftypes.UUID) *core.FFI); ok {
+	var r0 *fftypes.FFI
+	if rf, ok := ret.Get(0).(func(context.Context, string, *fftypes.UUID) *fftypes.FFI); ok {
 		r0 = rf(ctx, namespace, id)
 	} else {
 		if ret.Get(0) != nil {
-			r0 = ret.Get(0).(*core.FFI)
+			r0 = ret.Get(0).(*fftypes.FFI)
 		}
 	}
 
@@ -838,15 +838,15 @@ func (_m *Plugin) GetFFIByID(ctx context.Context, namespace string, id *fftypes.
 }
 
 // GetFFIEvent provides a mock function with given fields: ctx, namespace, interfaceID, pathName
-func (_m *Plugin) GetFFIEvent(ctx context.Context, namespace string, interfaceID *fftypes.UUID, pathName string) (*core.FFIEvent, error) {
+func (_m *Plugin) GetFFIEvent(ctx context.Context, namespace string, interfaceID *fftypes.UUID, pathName string) (*fftypes.FFIEvent, error) {
 	ret := _m.Called(ctx, namespace, interfaceID, pathName)
 
-	var r0 *core.FFIEvent
-	if rf, ok := ret.Get(0).(func(context.Context, string, *fftypes.UUID, string) *core.FFIEvent); ok {
+	var r0 *fftypes.FFIEvent
+	if rf, ok := ret.Get(0).(func(context.Context, string, *fftypes.UUID, string) *fftypes.FFIEvent); ok {
 		r0 = rf(ctx, namespace, interfaceID, pathName)
 	} else {
 		if ret.Get(0) != nil {
-			r0 = ret.Get(0).(*core.FFIEvent)
+			r0 = ret.Get(0).(*fftypes.FFIEvent)
 		}
 	}
 
@@ -861,15 +861,15 @@ func (_m *Plugin) GetFFIEvent(ctx context.Context, namespace string, interfaceID
 }
 
 // GetFFIEvents provides a mock function with given fields: ctx, namespace, filter
-func (_m *Plugin) GetFFIEvents(ctx context.Context, namespace string, filter database.Filter) ([]*core.FFIEvent, *database.FilterResult, error) {
+func (_m *Plugin) GetFFIEvents(ctx context.Context, namespace string, filter database.Filter) ([]*fftypes.FFIEvent, *database.FilterResult, error) {
 	ret := _m.Called(ctx, namespace, filter)
 
-	var r0 []*core.FFIEvent
-	if rf, ok := ret.Get(0).(func(context.Context, string, database.Filter) []*core.FFIEvent); ok {
+	var r0 []*fftypes.FFIEvent
+	if rf, ok := ret.Get(0).(func(context.Context, string, database.Filter) []*fftypes.FFIEvent); ok {
 		r0 = rf(ctx, namespace, filter)
 	} else {
 		if ret.Get(0) != nil {
-			r0 = ret.Get(0).([]*core.FFIEvent)
+			r0 = ret.Get(0).([]*fftypes.FFIEvent)
 		}
 	}
 
@@ -893,15 +893,15 @@ func (_m *Plugin) GetFFIEvents(ctx context.Context, namespace string, filter dat
 }
 
 // GetFFIMethod provides a mock function with given fields: ctx, namespace, interfaceID, pathName
-func (_m *Plugin) GetFFIMethod(ctx context.Context, namespace string, interfaceID *fftypes.UUID, pathName string) (*core.FFIMethod, error) {
+func (_m *Plugin) GetFFIMethod(ctx context.Context, namespace string, interfaceID *fftypes.UUID, pathName string) (*fftypes.FFIMethod, error) {
 	ret := _m.Called(ctx, namespace, interfaceID, pathName)
 
-	var r0 *core.FFIMethod
-	if rf, ok := ret.Get(0).(func(context.Context, string, *fftypes.UUID, string) *core.FFIMethod); ok {
+	var r0 *fftypes.FFIMethod
+	if rf, ok := ret.Get(0).(func(context.Context, string, *fftypes.UUID, string) *fftypes.FFIMethod); ok {
 		r0 = rf(ctx, namespace, interfaceID, pathName)
 	} else {
 		if ret.Get(0) != nil {
-			r0 = ret.Get(0).(*core.FFIMethod)
+			r0 = ret.Get(0).(*fftypes.FFIMethod)
 		}
 	}
 
@@ -916,15 +916,15 @@ func (_m *Plugin) GetFFIMethod(ctx context.Context, namespace string, interfaceI
 }
 
 // GetFFIMethods provides a mock function with given fields: ctx, namespace, filter
-func (_m *Plugin) GetFFIMethods(ctx context.Context, namespace string, filter database.Filter) ([]*core.FFIMethod, *database.FilterResult, error) {
+func (_m *Plugin) GetFFIMethods(ctx context.Context, namespace string, filter database.Filter) ([]*fftypes.FFIMethod, *database.FilterResult, error) {
 	ret := _m.Called(ctx, namespace, filter)
 
-	var r0 []*core.FFIMethod
-	if rf, ok := ret.Get(0).(func(context.Context, string, database.Filter) []*core.FFIMethod); ok {
+	var r0 []*fftypes.FFIMethod
+	if rf, ok := ret.Get(0).(func(context.Context, string, database.Filter) []*fftypes.FFIMethod); ok {
 		r0 = rf(ctx, namespace, filter)
 	} else {
 		if ret.Get(0) != nil {
-			r0 = ret.Get(0).([]*core.FFIMethod)
+			r0 = ret.Get(0).([]*fftypes.FFIMethod)
 		}
 	}
 
@@ -948,15 +948,15 @@ func (_m *Plugin) GetFFIMethods(ctx context.Context, namespace string, filter da
 }
 
 // GetFFIs provides a mock function with given fields: ctx, namespace, filter
-func (_m *Plugin) GetFFIs(ctx context.Context, namespace string, filter database.Filter) ([]*core.FFI, *database.FilterResult, error) {
+func (_m *Plugin) GetFFIs(ctx context.Context, namespace string, filter database.Filter) ([]*fftypes.FFI, *database.FilterResult, error) {
 	ret := _m.Called(ctx, namespace, filter)
 
-	var r0 []*core.FFI
-	if rf, ok := ret.Get(0).(func(context.Context, string, database.Filter) []*core.FFI); ok {
+	var r0 []*fftypes.FFI
+	if rf, ok := ret.Get(0).(func(context.Context, string, database.Filter) []*fftypes.FFI); ok {
 		r0 = rf(ctx, namespace, filter)
 	} else {
 		if ret.Get(0) != nil {
-			r0 = ret.Get(0).([]*core.FFI)
+			r0 = ret.Get(0).([]*fftypes.FFI)
 		}
 	}
 
@@ -2654,11 +2654,11 @@ func (_m *Plugin) UpsertDatatype(ctx context.Context, datadef *core.Datatype, al
 }
 
 // UpsertFFI provides a mock function with given fields: ctx, cd
-func (_m *Plugin) UpsertFFI(ctx context.Context, cd *core.FFI) error {
+func (_m *Plugin) UpsertFFI(ctx context.Context, cd *fftypes.FFI) error {
 	ret := _m.Called(ctx, cd)
 
 	var r0 error
-	if rf, ok := ret.Get(0).(func(context.Context, *core.FFI) error); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *fftypes.FFI) error); ok {
 		r0 = rf(ctx, cd)
 	} else {
 		r0 = ret.Error(0)
@@ -2668,11 +2668,11 @@ func (_m *Plugin) UpsertFFI(ctx context.Context, cd *core.FFI) error {
 }
 
 // UpsertFFIEvent provides a mock function with given fields: ctx, method
-func (_m *Plugin) UpsertFFIEvent(ctx context.Context, method *core.FFIEvent) error {
+func (_m *Plugin) UpsertFFIEvent(ctx context.Context, method *fftypes.FFIEvent) error {
 	ret := _m.Called(ctx, method)
 
 	var r0 error
-	if rf, ok := ret.Get(0).(func(context.Context, *core.FFIEvent) error); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *fftypes.FFIEvent) error); ok {
 		r0 = rf(ctx, method)
 	} else {
 		r0 = ret.Error(0)
@@ -2682,11 +2682,11 @@ func (_m *Plugin) UpsertFFIEvent(ctx context.Context, method *core.FFIEvent) err
 }
 
 // UpsertFFIMethod provides a mock function with given fields: ctx, method
-func (_m *Plugin) UpsertFFIMethod(ctx context.Context, method *core.FFIMethod) error {
+func (_m *Plugin) UpsertFFIMethod(ctx context.Context, method *fftypes.FFIMethod) error {
 	ret := _m.Called(ctx, method)
 
 	var r0 error
-	if rf, ok := ret.Get(0).(func(context.Context, *core.FFIMethod) error); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *fftypes.FFIMethod) error); ok {
 		r0 = rf(ctx, method)
 	} else {
 		r0 = ret.Error(0)
diff --git a/mocks/definitionsmocks/sender.go b/mocks/definitionsmocks/sender.go
index 102ecbfa2..0d46bb82c 100644
--- a/mocks/definitionsmocks/sender.go
+++ b/mocks/definitionsmocks/sender.go
@@ -7,6 +7,8 @@ import (
 
 	core "github.com/hyperledger/firefly/pkg/core"
 
+	fftypes "github.com/hyperledger/firefly-common/pkg/fftypes"
+
 	mock "github.com/stretchr/testify/mock"
 )
 
@@ -58,11 +60,11 @@ func (_m *Sender) DefineDatatype(ctx context.Context, datatype *core.Datatype, w
 }
 
 // DefineFFI provides a mock function with given fields: ctx, ffi, waitConfirm
-func (_m *Sender) DefineFFI(ctx context.Context, ffi *core.FFI, waitConfirm bool) error {
+func (_m *Sender) DefineFFI(ctx context.Context, ffi *fftypes.FFI, waitConfirm bool) error {
 	ret := _m.Called(ctx, ffi, waitConfirm)
 
 	var r0 error
-	if rf, ok := ret.Get(0).(func(context.Context, *core.FFI, bool) error); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *fftypes.FFI, bool) error); ok {
 		r0 = rf(ctx, ffi, waitConfirm)
 	} else {
 		r0 = ret.Error(0)
diff --git a/pkg/blockchain/plugin.go b/pkg/blockchain/plugin.go
index 46e885821..bae6c8a41 100644
--- a/pkg/blockchain/plugin.go
+++ b/pkg/blockchain/plugin.go
@@ -60,10 +60,10 @@ type Plugin interface {
 	SubmitNetworkAction(ctx context.Context, nsOpID string, signingKey string, action core.NetworkActionType, location *fftypes.JSONAny) error
 
 	// InvokeContract submits a new transaction to be executed by custom on-chain logic
-	InvokeContract(ctx context.Context, nsOpID string, signingKey string, location *fftypes.JSONAny, method *core.FFIMethod, input map[string]interface{}, options map[string]interface{}) error
+	InvokeContract(ctx context.Context, nsOpID string, signingKey string, location *fftypes.JSONAny, method *fftypes.FFIMethod, input map[string]interface{}, options map[string]interface{}) error
 
 	// QueryContract executes a method via custom on-chain logic and returns the result
-	QueryContract(ctx context.Context, location *fftypes.JSONAny, method *core.FFIMethod, input map[string]interface{}, options map[string]interface{}) (interface{}, error)
+	QueryContract(ctx context.Context, location *fftypes.JSONAny, method *fftypes.FFIMethod, input map[string]interface{}, options map[string]interface{}) (interface{}, error)
 
 	// AddContractListener adds a new subscription to a user-specified contract and event
 	AddContractListener(ctx context.Context, subscription *core.ContractListenerInput) error
@@ -72,16 +72,16 @@ type Plugin interface {
 	DeleteContractListener(ctx context.Context, subscription *core.ContractListener) error
 
 	// GetFFIParamValidator returns a blockchain-plugin-specific validator for FFIParams and their JSON Schema
-	GetFFIParamValidator(ctx context.Context) (core.FFIParamValidator, error)
+	GetFFIParamValidator(ctx context.Context) (fftypes.FFIParamValidator, error)
 
 	// GenerateFFI returns an FFI from a blockchain specific interface format e.g. an Ethereum ABI
-	GenerateFFI(ctx context.Context, generationRequest *core.FFIGenerationRequest) (*core.FFI, error)
+	GenerateFFI(ctx context.Context, generationRequest *fftypes.FFIGenerationRequest) (*fftypes.FFI, error)
 
 	// NormalizeContractLocation validates and normalizes the formatting of the location JSON
 	NormalizeContractLocation(ctx context.Context, location *fftypes.JSONAny) (*fftypes.JSONAny, error)
 
 	// GenerateEventSignature generates a strigified signature for the event, incorporating any fields significant to identifying the event as unique
-	GenerateEventSignature(ctx context.Context, event *core.FFIEventDefinition) string
+	GenerateEventSignature(ctx context.Context, event *fftypes.FFIEventDefinition) string
 
 	// GetNetworkVersion queries the provided contract to get the network version
 	GetNetworkVersion(ctx context.Context, location *fftypes.JSONAny) (int, error)
diff --git a/pkg/core/contract_listener.go b/pkg/core/contract_listener.go
index 2b9a3d3b4..d68e65b25 100644
--- a/pkg/core/contract_listener.go
+++ b/pkg/core/contract_listener.go
@@ -27,7 +27,7 @@ import (
 
 type ContractListener struct {
 	ID        *fftypes.UUID            `ffstruct:"ContractListener" json:"id,omitempty" ffexcludeinput:"true"`
-	Interface *FFIReference            `ffstruct:"ContractListener" json:"interface,omitempty" ffexcludeinput:"postContractAPIListeners"`
+	Interface *fftypes.FFIReference    `ffstruct:"ContractListener" json:"interface,omitempty" ffexcludeinput:"postContractAPIListeners"`
 	Namespace string                   `ffstruct:"ContractListener" json:"namespace,omitempty" ffexcludeinput:"true"`
 	Name      string                   `ffstruct:"ContractListener" json:"name,omitempty"`
 	BackendID string                   `ffstruct:"ContractListener" json:"backendId,omitempty" ffexcludeinput:"true"`
@@ -49,7 +49,7 @@ type ContractListenerInput struct {
 }
 
 type FFISerializedEvent struct {
-	FFIEventDefinition
+	fftypes.FFIEventDefinition
 }
 
 // Scan implements sql.Scanner
diff --git a/pkg/core/contracts.go b/pkg/core/contracts.go
index 7dc69484b..69a6289c9 100644
--- a/pkg/core/contracts.go
+++ b/pkg/core/contracts.go
@@ -36,7 +36,7 @@ type ContractCallRequest struct {
 	Interface  *fftypes.UUID          `ffstruct:"ContractCallRequest" json:"interface,omitempty" ffexcludeinput:"postContractAPIInvoke,postContractAPIQuery"`
 	Location   *fftypes.JSONAny       `ffstruct:"ContractCallRequest" json:"location,omitempty"`
 	Key        string                 `ffstruct:"ContractCallRequest" json:"key,omitempty"`
-	Method     *FFIMethod             `ffstruct:"ContractCallRequest" json:"method,omitempty" ffexcludeinput:"postContractAPIInvoke,postContractAPIQuery"`
+	Method     *fftypes.FFIMethod     `ffstruct:"ContractCallRequest" json:"method,omitempty" ffexcludeinput:"postContractAPIInvoke,postContractAPIQuery"`
 	MethodPath string                 `ffstruct:"ContractCallRequest" json:"methodPath,omitempty" ffexcludeinput:"postContractAPIInvoke,postContractAPIQuery"`
 	Input      map[string]interface{} `ffstruct:"ContractCallRequest" json:"input"`
 	Options    map[string]interface{} `ffstruct:"ContractCallRequest" json:"options"`
@@ -48,27 +48,27 @@ type ContractURLs struct {
 }
 
 type ContractAPI struct {
-	ID        *fftypes.UUID    `ffstruct:"ContractAPI" json:"id,omitempty" ffexcludeinput:"true"`
-	Namespace string           `ffstruct:"ContractAPI" json:"namespace,omitempty" ffexcludeinput:"true"`
-	Interface *FFIReference    `ffstruct:"ContractAPI" json:"interface"`
-	Location  *fftypes.JSONAny `ffstruct:"ContractAPI" json:"location,omitempty"`
-	Name      string           `ffstruct:"ContractAPI" json:"name"`
-	Message   *fftypes.UUID    `ffstruct:"ContractAPI" json:"message,omitempty" ffexcludeinput:"true"`
-	URLs      ContractURLs     `ffstruct:"ContractAPI" json:"urls" ffexcludeinput:"true"`
+	ID        *fftypes.UUID         `ffstruct:"ContractAPI" json:"id,omitempty" ffexcludeinput:"true"`
+	Namespace string                `ffstruct:"ContractAPI" json:"namespace,omitempty" ffexcludeinput:"true"`
+	Interface *fftypes.FFIReference `ffstruct:"ContractAPI" json:"interface"`
+	Location  *fftypes.JSONAny      `ffstruct:"ContractAPI" json:"location,omitempty"`
+	Name      string                `ffstruct:"ContractAPI" json:"name"`
+	Message   *fftypes.UUID         `ffstruct:"ContractAPI" json:"message,omitempty" ffexcludeinput:"true"`
+	URLs      ContractURLs          `ffstruct:"ContractAPI" json:"urls" ffexcludeinput:"true"`
 }
 
 func (c *ContractAPI) Validate(ctx context.Context, existing bool) (err error) {
-	if err = ValidateFFNameField(ctx, c.Namespace, "namespace"); err != nil {
+	if err = fftypes.ValidateFFNameField(ctx, c.Namespace, "namespace"); err != nil {
 		return err
 	}
-	if err = ValidateFFNameField(ctx, c.Name, "name"); err != nil {
+	if err = fftypes.ValidateFFNameField(ctx, c.Name, "name"); err != nil {
 		return err
 	}
 	return nil
 }
 
 func (c *ContractAPI) Topic() string {
-	return typeNamespaceNameTopicHash("contractapi", c.Namespace, c.Name)
+	return fftypes.TypeNamespaceNameTopicHash("contractapi", c.Namespace, c.Name)
 }
 
 func (c *ContractAPI) SetBroadcastMessage(msgID *fftypes.UUID) {
diff --git a/pkg/core/datatype.go b/pkg/core/datatype.go
index 1253d6803..a66e86973 100644
--- a/pkg/core/datatype.go
+++ b/pkg/core/datatype.go
@@ -51,13 +51,13 @@ func (dt *Datatype) Validate(ctx context.Context, existing bool) (err error) {
 	if dt.Validator != ValidatorTypeJSON {
 		return i18n.NewError(ctx, i18n.MsgUnknownFieldValue, "validator", dt.Validator)
 	}
-	if err = ValidateFFNameField(ctx, dt.Namespace, "namespace"); err != nil {
+	if err = fftypes.ValidateFFNameField(ctx, dt.Namespace, "namespace"); err != nil {
 		return err
 	}
-	if err = ValidateFFNameFieldNoUUID(ctx, dt.Name, "name"); err != nil {
+	if err = fftypes.ValidateFFNameFieldNoUUID(ctx, dt.Name, "name"); err != nil {
 		return err
 	}
-	if err = ValidateFFNameField(ctx, dt.Version, "version"); err != nil {
+	if err = fftypes.ValidateFFNameField(ctx, dt.Version, "version"); err != nil {
 		return err
 	}
 	if dt.Value == nil || len(*dt.Value) == 0 {
@@ -76,7 +76,7 @@ func (dt *Datatype) Validate(ctx context.Context, existing bool) (err error) {
 }
 
 func (dt *Datatype) Topic() string {
-	return typeNamespaceNameTopicHash("datatype", dt.Namespace, dt.Name)
+	return fftypes.TypeNamespaceNameTopicHash("datatype", dt.Namespace, dt.Name)
 }
 
 func (dt *Datatype) SetBroadcastMessage(msgID *fftypes.UUID) {
diff --git a/pkg/core/event.go b/pkg/core/event.go
index afeb43ea4..170b8644f 100644
--- a/pkg/core/event.go
+++ b/pkg/core/event.go
@@ -79,7 +79,7 @@ type EnrichedEvent struct {
 	Event
 	BlockchainEvent   *BlockchainEvent `ffstruct:"EnrichedEvent" json:"blockchainEvent,omitempty"`
 	ContractAPI       *ContractAPI     `ffstruct:"EnrichedEvent" json:"contractAPI,omitempty"`
-	ContractInterface *FFI             `ffstruct:"EnrichedEvent" json:"contractInterface,omitempty"`
+	ContractInterface *fftypes.FFI     `ffstruct:"EnrichedEvent" json:"contractInterface,omitempty"`
 	Datatype          *Datatype        `ffstruct:"EnrichedEvent" json:"datatype,omitempty"`
 	Identity          *Identity        `ffstruct:"EnrichedEvent" json:"identity,omitempty"`
 	Message           *Message         `ffstruct:"EnrichedEvent" json:"message,omitempty"`
diff --git a/pkg/core/ffi.go b/pkg/core/ffi.go
deleted file mode 100644
index 504cacf80..000000000
--- a/pkg/core/ffi.go
+++ /dev/null
@@ -1,150 +0,0 @@
-// Copyright © 2022 Kaleido, Inc.
-//
-// SPDX-License-Identifier: Apache-2.0
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package core
-
-import (
-	"context"
-	"database/sql/driver"
-	"encoding/json"
-
-	"github.com/hyperledger/firefly-common/pkg/fftypes"
-	"github.com/hyperledger/firefly-common/pkg/i18n"
-	"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
-	GetExtensionName() string
-}
-
-type FFIReference struct {
-	ID      *fftypes.UUID `ffstruct:"FFIReference" json:"id,omitempty"`
-	Name    string        `ffstruct:"FFIReference" json:"name,omitempty"`
-	Version string        `ffstruct:"FFIReference" json:"version,omitempty"`
-}
-
-type FFI struct {
-	ID          *fftypes.UUID `ffstruct:"FFI" json:"id,omitempty" ffexcludeinput:"true"`
-	Message     *fftypes.UUID `ffstruct:"FFI" json:"message,omitempty" ffexcludeinput:"true"`
-	Namespace   string        `ffstruct:"FFI" json:"namespace,omitempty" ffexcludeinput:"true"`
-	Name        string        `ffstruct:"FFI" json:"name"`
-	Description string        `ffstruct:"FFI" json:"description"`
-	Version     string        `ffstruct:"FFI" json:"version"`
-	Methods     []*FFIMethod  `ffstruct:"FFI" json:"methods,omitempty"`
-	Events      []*FFIEvent   `ffstruct:"FFI" json:"events,omitempty"`
-}
-
-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"`
-	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"`
-	Details     fftypes.JSONObject `ffstruct:"FFIEvent" json:"details,omitempty"`
-}
-
-type FFIEvent struct {
-	ID        *fftypes.UUID `ffstruct:"FFIEvent" json:"id,omitempty" ffexcludeinput:"true"`
-	Interface *fftypes.UUID `ffstruct:"FFIEvent" json:"interface,omitempty" ffexcludeinput:"true"`
-	Namespace string        `ffstruct:"FFIEvent" json:"namespace,omitempty" ffexcludeinput:"true"`
-	Pathname  string        `ffstruct:"FFIEvent" json:"pathname,omitempty" ffexcludeinput:"true"`
-	Signature string        `ffstruct:"FFIEvent" json:"signature" ffexcludeinput:"true"`
-	FFIEventDefinition
-}
-
-type FFIParam struct {
-	Name   string           `ffstruct:"FFIParam" json:"name"`
-	Schema *fftypes.JSONAny `ffstruct:"FFIParam" json:"schema,omitempty"`
-}
-
-type FFIParams []*FFIParam
-
-type FFIGenerationRequest struct {
-	Namespace   string           `ffstruct:"FFIGenerationRequest" json:"namespace,omitempty"`
-	Name        string           `ffstruct:"FFIGenerationRequest" json:"name"`
-	Description string           `ffstruct:"FFIGenerationRequest" json:"description"`
-	Version     string           `ffstruct:"FFIGenerationRequest" json:"version"`
-	Input       *fftypes.JSONAny `ffstruct:"FFIGenerationRequest" json:"input"`
-}
-
-func (f *FFI) Validate(ctx context.Context, existing bool) (err error) {
-	if err = ValidateFFNameField(ctx, f.Namespace, "namespace"); err != nil {
-		return err
-	}
-	if err = ValidateFFNameField(ctx, f.Name, "name"); err != nil {
-		return err
-	}
-	if err = ValidateFFNameField(ctx, f.Version, "version"); err != nil {
-		return err
-	}
-	return nil
-}
-
-func (f *FFI) Topic() string {
-	return typeNamespaceNameTopicHash("ffi", f.Namespace, f.Name)
-}
-
-func (f *FFI) SetBroadcastMessage(msgID *fftypes.UUID) {
-	f.Message = msgID
-}
-
-// Scan implements sql.Scanner
-func (p *FFIParams) Scan(src interface{}) error {
-	switch src := src.(type) {
-	case nil:
-		p = nil
-		return nil
-	case string:
-		return json.Unmarshal([]byte(src), &p)
-	case []byte:
-		return json.Unmarshal(src, &p)
-	default:
-		return i18n.NewError(context.Background(), i18n.MsgTypeRestoreFailed, src, p)
-	}
-}
-
-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
deleted file mode 100644
index 2ac925686..000000000
--- a/pkg/core/ffi_param_validator.go
+++ /dev/null
@@ -1,96 +0,0 @@
-// Copyright © 2022 Kaleido, Inc.
-//
-// SPDX-License-Identifier: Apache-2.0
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package core
-
-import (
-	"github.com/santhosh-tekuri/jsonschema/v5"
-)
-
-type BaseFFIParamValidator struct{}
-
-func (v BaseFFIParamValidator) Compile(ctx jsonschema.CompilerContext, m map[string]interface{}) (jsonschema.ExtSchema, error) {
-	return nil, nil
-}
-
-func (v *BaseFFIParamValidator) GetMetaSchema() *jsonschema.Schema {
-	return jsonschema.MustCompileString("ffi.json", `{
-		"$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"
-						]
-					}
-				]
-			}
-		}
-	}`)
-}
-
-func (v *BaseFFIParamValidator) GetExtensionName() string {
-	return "ffi"
-}
-
-func NewFFISchemaCompiler() *jsonschema.Compiler {
-	c := jsonschema.NewCompiler()
-	c.Draft = jsonschema.Draft2020
-	v := BaseFFIParamValidator{}
-	c.RegisterExtension(v.GetExtensionName(), v.GetMetaSchema(), v)
-	return c
-}
diff --git a/pkg/core/group.go b/pkg/core/group.go
index 3fc446a9a..9e30ab8d7 100644
--- a/pkg/core/group.go
+++ b/pkg/core/group.go
@@ -63,12 +63,12 @@ func (man *GroupIdentity) Hash() *fftypes.Bytes32 {
 }
 
 func (group *Group) Validate(ctx context.Context, existing bool) (err error) {
-	if err = ValidateFFNameField(ctx, group.Namespace, "namespace"); err != nil {
+	if err = fftypes.ValidateFFNameField(ctx, group.Namespace, "namespace"); err != nil {
 		return err
 	}
 	// We allow a blank name for a group (for auto creation)
 	if group.Name != "" {
-		if err = ValidateFFNameFieldNoUUID(ctx, group.Name, "name"); err != nil {
+		if err = fftypes.ValidateFFNameFieldNoUUID(ctx, group.Name, "name"); err != nil {
 			return err
 		}
 	}
@@ -80,7 +80,7 @@ func (group *Group) Validate(ctx context.Context, existing bool) (err error) {
 		if r.Identity == "" {
 			return i18n.NewError(ctx, i18n.MsgEmptyMemberIdentity, i)
 		}
-		if err = ValidateLength(ctx, r.Identity, "identity", 1024); err != nil {
+		if err = fftypes.ValidateLength(ctx, r.Identity, "identity", 1024); err != nil {
 			return err
 		}
 		if r.Node == nil {
diff --git a/pkg/core/identity.go b/pkg/core/identity.go
index a64761dee..7dba1171a 100644
--- a/pkg/core/identity.go
+++ b/pkg/core/identity.go
@@ -166,10 +166,10 @@ func (i *IdentityBase) Validate(ctx context.Context) (err error) {
 	if i.ID == nil {
 		return i18n.NewError(ctx, i18n.MsgNilID)
 	}
-	if err = ValidateFFNameFieldNoUUID(ctx, i.Namespace, "namespace"); err != nil {
+	if err = fftypes.ValidateFFNameFieldNoUUID(ctx, i.Namespace, "namespace"); err != nil {
 		return err
 	}
-	if err = ValidateFFNameFieldNoUUID(ctx, i.Name, "name"); err != nil {
+	if err = fftypes.ValidateFFNameFieldNoUUID(ctx, i.Name, "name"); err != nil {
 		return err
 	}
 
@@ -231,7 +231,7 @@ func (identity *Identity) Validate(ctx context.Context) (err error) {
 	if err = identity.IdentityBase.Validate(ctx); err != nil {
 		return err
 	}
-	if err = ValidateLength(ctx, identity.Description, "description", 4096); err != nil {
+	if err = fftypes.ValidateLength(ctx, identity.Description, "description", 4096); err != nil {
 		return err
 	}
 	return nil
diff --git a/pkg/core/message.go b/pkg/core/message.go
index 7fe672ff0..763937e6e 100644
--- a/pkg/core/message.go
+++ b/pkg/core/message.go
@@ -229,7 +229,7 @@ func (m *Message) VerifyFields(ctx context.Context) error {
 		return err
 	}
 	if m.Header.Tag != "" {
-		if err := ValidateFFNameField(ctx, m.Header.Tag, "header.tag"); err != nil {
+		if err := fftypes.ValidateFFNameField(ctx, m.Header.Tag, "header.tag"); err != nil {
 			return err
 		}
 	}
diff --git a/pkg/core/namespace.go b/pkg/core/namespace.go
index 8593bc04c..851add13d 100644
--- a/pkg/core/namespace.go
+++ b/pkg/core/namespace.go
@@ -18,7 +18,6 @@ package core
 
 import (
 	"context"
-	"crypto/sha256"
 	"database/sql/driver"
 	"encoding/json"
 
@@ -76,10 +75,10 @@ type NetworkAction struct {
 }
 
 func (ns *Namespace) Validate(ctx context.Context, existing bool) (err error) {
-	if err = ValidateFFNameField(ctx, ns.Name, "name"); err != nil {
+	if err = fftypes.ValidateFFNameField(ctx, ns.Name, "name"); err != nil {
 		return err
 	}
-	if err = ValidateLength(ctx, ns.Description, "description", 4096); err != nil {
+	if err = fftypes.ValidateLength(ctx, ns.Description, "description", 4096); err != nil {
 		return err
 	}
 	if existing {
@@ -90,18 +89,8 @@ func (ns *Namespace) Validate(ctx context.Context, existing bool) (err error) {
 	return nil
 }
 
-func typeNamespaceNameTopicHash(objType string, ns string, name string) string {
-	// Topic generation function for ordering anything with a type, namespace and name.
-	// Means all messages racing for this name will be consistently ordered by all parties.
-	h := sha256.New()
-	h.Write([]byte(objType))
-	h.Write([]byte(ns))
-	h.Write([]byte(name))
-	return fftypes.HashResult(h).String()
-}
-
 func (ns *Namespace) Topic() string {
-	return typeNamespaceNameTopicHash("namespace", ns.Name, "")
+	return fftypes.TypeNamespaceNameTopicHash("namespace", ns.Name, "")
 }
 
 func (ns *Namespace) SetBroadcastMessage(msgID *fftypes.UUID) {
diff --git a/pkg/core/operation.go b/pkg/core/operation.go
index acba99fc3..5bf25c357 100644
--- a/pkg/core/operation.go
+++ b/pkg/core/operation.go
@@ -135,7 +135,7 @@ func ParseNamespacedOpID(ctx context.Context, nsIDStr string) (string, *fftypes.
 	}
 	ns := nsIDSplit[0]
 	uuidStr := nsIDSplit[1]
-	if err := ValidateFFNameField(ctx, ns, "namespace"); err != nil {
+	if err := fftypes.ValidateFFNameField(ctx, ns, "namespace"); err != nil {
 		return "", nil, err
 	}
 	u, err := fftypes.ParseUUID(ctx, uuidStr)
diff --git a/pkg/core/stringarray.go b/pkg/core/stringarray.go
index ee40585d0..4b32ab69f 100644
--- a/pkg/core/stringarray.go
+++ b/pkg/core/stringarray.go
@@ -23,6 +23,7 @@ import (
 	"sort"
 	"strings"
 
+	"github.com/hyperledger/firefly-common/pkg/fftypes"
 	"github.com/hyperledger/firefly-common/pkg/i18n"
 )
 
@@ -97,11 +98,11 @@ func (sa FFStringArray) Validate(ctx context.Context, fieldName string, isName b
 		dupCheck[n] = true
 		totalLength += len(n)
 		if isName {
-			if err := ValidateFFNameField(ctx, n, fmt.Sprintf("%s[%d]", fieldName, i)); err != nil {
+			if err := fftypes.ValidateFFNameField(ctx, n, fmt.Sprintf("%s[%d]", fieldName, i)); err != nil {
 				return err
 			}
 		} else {
-			if err := ValidateSafeCharsOnly(ctx, n, fmt.Sprintf("%s[%d]", fieldName, i)); err != nil {
+			if err := fftypes.ValidateSafeCharsOnly(ctx, n, fmt.Sprintf("%s[%d]", fieldName, i)); err != nil {
 				return err
 			}
 		}
diff --git a/pkg/core/tokenpool.go b/pkg/core/tokenpool.go
index e9e30273c..b8cf10b43 100644
--- a/pkg/core/tokenpool.go
+++ b/pkg/core/tokenpool.go
@@ -66,17 +66,17 @@ type TokenPoolAnnouncement struct {
 }
 
 func (t *TokenPool) Validate(ctx context.Context) (err error) {
-	if err = ValidateFFNameField(ctx, t.Namespace, "namespace"); err != nil {
+	if err = fftypes.ValidateFFNameField(ctx, t.Namespace, "namespace"); err != nil {
 		return err
 	}
-	if err = ValidateFFNameFieldNoUUID(ctx, t.Name, "name"); err != nil {
+	if err = fftypes.ValidateFFNameFieldNoUUID(ctx, t.Name, "name"); err != nil {
 		return err
 	}
 	return nil
 }
 
 func (t *TokenPoolAnnouncement) Topic() string {
-	return typeNamespaceNameTopicHash("tokenpool", t.Pool.Namespace, t.Pool.Name)
+	return fftypes.TypeNamespaceNameTopicHash("tokenpool", t.Pool.Namespace, t.Pool.Name)
 }
 
 func (t *TokenPoolAnnouncement) SetBroadcastMessage(msgID *fftypes.UUID) {
diff --git a/pkg/core/validations.go b/pkg/core/validations.go
deleted file mode 100644
index 18ee13609..000000000
--- a/pkg/core/validations.go
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright © 2022 Kaleido, Inc.
-//
-// SPDX-License-Identifier: Apache-2.0
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package core
-
-import (
-	"context"
-	"regexp"
-
-	"github.com/hyperledger/firefly-common/pkg/fftypes"
-	"github.com/hyperledger/firefly-common/pkg/i18n"
-)
-
-var (
-	ffNameValidator      = regexp.MustCompile(`^[0-9a-zA-Z]([0-9a-zA-Z._-]{0,62}[0-9a-zA-Z])?$`)
-	ffSafeCharsValidator = regexp.MustCompile(`^[0-9a-zA-Z._-]*$`)
-)
-
-func ValidateSafeCharsOnly(ctx context.Context, str string, fieldName string) error {
-	if !ffSafeCharsValidator.MatchString(str) {
-		return i18n.NewError(ctx, i18n.MsgSafeCharsOnly, fieldName)
-	}
-	return nil
-}
-
-func ValidateFFNameField(ctx context.Context, str string, fieldName string) error {
-	if !ffNameValidator.MatchString(str) {
-		return i18n.NewError(ctx, i18n.MsgInvalidName, fieldName)
-	}
-	return nil
-}
-
-func ValidateFFNameFieldNoUUID(ctx context.Context, str string, fieldName string) error {
-	if _, err := fftypes.ParseUUID(ctx, str); err == nil {
-		// Name must not be a UUID
-		return i18n.NewError(ctx, i18n.MsgNoUUID, fieldName)
-	}
-	return ValidateFFNameField(ctx, str, fieldName)
-}
-
-func ValidateLength(ctx context.Context, str string, fieldName string, max int) error {
-	if len([]byte(str)) > max {
-		return i18n.NewError(ctx, i18n.MsgFieldTooLong, fieldName, max)
-	}
-	return nil
-}
diff --git a/pkg/database/plugin.go b/pkg/database/plugin.go
index 232721fa3..abb1b85ce 100644
--- a/pkg/database/plugin.go
+++ b/pkg/database/plugin.go
@@ -429,38 +429,38 @@ type iTokenApprovalCollection interface {
 
 type iFFICollection interface {
 	// UpsertFFI - Upsert an FFI
-	UpsertFFI(ctx context.Context, cd *core.FFI) error
+	UpsertFFI(ctx context.Context, cd *fftypes.FFI) error
 
 	// GetFFIs - Get FFIs
-	GetFFIs(ctx context.Context, namespace string, filter Filter) ([]*core.FFI, *FilterResult, error)
+	GetFFIs(ctx context.Context, namespace string, filter Filter) ([]*fftypes.FFI, *FilterResult, error)
 
 	// GetFFIByID - Get an FFI by ID
-	GetFFIByID(ctx context.Context, namespace string, id *fftypes.UUID) (*core.FFI, error)
+	GetFFIByID(ctx context.Context, namespace string, id *fftypes.UUID) (*fftypes.FFI, error)
 
 	// GetFFI - Get an FFI by name and version
-	GetFFI(ctx context.Context, namespace, name, version string) (*core.FFI, error)
+	GetFFI(ctx context.Context, namespace, name, version string) (*fftypes.FFI, error)
 }
 
 type iFFIMethodCollection interface {
 	// UpsertFFIMethod - Upsert an FFI method
-	UpsertFFIMethod(ctx context.Context, method *core.FFIMethod) error
+	UpsertFFIMethod(ctx context.Context, method *fftypes.FFIMethod) error
 
 	// GetFFIMethod - Get an FFI method by path
-	GetFFIMethod(ctx context.Context, namespace string, interfaceID *fftypes.UUID, pathName string) (*core.FFIMethod, error)
+	GetFFIMethod(ctx context.Context, namespace string, interfaceID *fftypes.UUID, pathName string) (*fftypes.FFIMethod, error)
 
 	// GetFFIMethods - Get FFI methods
-	GetFFIMethods(ctx context.Context, namespace string, filter Filter) (methods []*core.FFIMethod, res *FilterResult, err error)
+	GetFFIMethods(ctx context.Context, namespace string, filter Filter) (methods []*fftypes.FFIMethod, res *FilterResult, err error)
 }
 
 type iFFIEventCollection interface {
 	// UpsertFFIEvent - Upsert an FFI event
-	UpsertFFIEvent(ctx context.Context, method *core.FFIEvent) error
+	UpsertFFIEvent(ctx context.Context, method *fftypes.FFIEvent) error
 
 	// GetFFIEvent - Get an FFI event by path
-	GetFFIEvent(ctx context.Context, namespace string, interfaceID *fftypes.UUID, pathName string) (*core.FFIEvent, error)
+	GetFFIEvent(ctx context.Context, namespace string, interfaceID *fftypes.UUID, pathName string) (*fftypes.FFIEvent, error)
 
 	// GetFFIEvents - Get FFI events
-	GetFFIEvents(ctx context.Context, namespace string, filter Filter) (events []*core.FFIEvent, res *FilterResult, err error)
+	GetFFIEvents(ctx context.Context, namespace string, filter Filter) (events []*fftypes.FFIEvent, res *FilterResult, err error)
 }
 
 type iContractAPICollection interface {
diff --git a/smart_contracts/ethereum/solidity_firefly/hardhat.config.ts b/smart_contracts/ethereum/solidity_firefly/hardhat.config.ts
index ac5b2cb56..0ac426474 100644
--- a/smart_contracts/ethereum/solidity_firefly/hardhat.config.ts
+++ b/smart_contracts/ethereum/solidity_firefly/hardhat.config.ts
@@ -14,10 +14,6 @@ const config: HardhatUserConfig = {
   networks: {
     'firefly-cli': {
       url: "http://127.0.0.1:5100"
-    },
-    rinkeby: {
-        url: `https://rinkeby.infura.io/v3/${process.env.INFURA_PROJECT_ID}`,
-        accounts: [`${process.env.RINKEBY_PRIVATE_KEY}`]
     }
   },
   gasReporter: {


Test patch:
diff --git a/internal/apiserver/ffi2swagger_test.go b/internal/apiserver/ffi2swagger_test.go
index e6a862b12..54dfd4e02 100644
--- a/internal/apiserver/ffi2swagger_test.go
+++ b/internal/apiserver/ffi2swagger_test.go
@@ -28,17 +28,17 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
-func testFFI() *core.FFI {
-	return &core.FFI{
+func testFFI() *fftypes.FFI {
+	return &fftypes.FFI{
 		ID:        fftypes.NewUUID(),
 		Namespace: "ns1",
 		Name:      "math",
 		Version:   "v1.0.0",
-		Methods: []*core.FFIMethod{
+		Methods: []*fftypes.FFIMethod{
 			{
 				Name:     "method1",
 				Pathname: "method1",
-				Params: core.FFIParams{
+				Params: fftypes.FFIParams{
 					{
 						Name:   "x",
 						Schema: fftypes.JSONAnyPtr(`{"type": "integer"}`),
@@ -59,7 +59,7 @@ func testFFI() *core.FFI {
 }`),
 					},
 				},
-				Returns: core.FFIParams{
+				Returns: fftypes.FFIParams{
 					{
 						Name:   "success",
 						Schema: fftypes.JSONAnyPtr(`{"type": "boolean"}`),
@@ -76,13 +76,13 @@ func testFFI() *core.FFI {
 				/* no params */
 			},
 		},
-		Events: []*core.FFIEvent{
+		Events: []*fftypes.FFIEvent{
 			{
 				ID:       fftypes.NewUUID(),
 				Pathname: "event1",
-				FFIEventDefinition: core.FFIEventDefinition{
+				FFIEventDefinition: fftypes.FFIEventDefinition{
 					Name: "event1",
-					Params: core.FFIParams{
+					Params: fftypes.FFIParams{
 						{
 							Name:   "result",
 							Schema: fftypes.JSONAnyPtr(`{"type": "integer"}`),
@@ -186,10 +186,21 @@ func TestGenerateWithLocation(t *testing.T) {
 }
 
 func TestFFIParamBadSchema(t *testing.T) {
-	param := &core.FFIParam{
-		Name:   "test",
-		Schema: fftypes.JSONAnyPtr(`{`),
+	params := &fftypes.FFIParams{
+		&fftypes.FFIParam{
+			Name:   "test",
+			Schema: fftypes.JSONAnyPtr(`{`),
+		},
+	}
+	_, err := contractJSONSchema(params, true)
+	assert.Error(t, err)
+
+	params = &fftypes.FFIParams{
+		&fftypes.FFIParam{
+			Name:   "test",
+			Schema: fftypes.JSONAnyPtr(`{"type": false}`),
+		},
 	}
-	r := ffiParamJSONSchema(param)
-	assert.Nil(t, r)
+	_, err = contractJSONSchema(params, true)
+	assert.Error(t, err)
 }
diff --git a/internal/apiserver/route_get_contract_api_interface_test.go b/internal/apiserver/route_get_contract_api_interface_test.go
index d906df2b6..59ef0e5ab 100644
--- a/internal/apiserver/route_get_contract_api_interface_test.go
+++ b/internal/apiserver/route_get_contract_api_interface_test.go
@@ -22,6 +22,7 @@ import (
 	"net/http/httptest"
 	"testing"
 
+	"github.com/hyperledger/firefly-common/pkg/fftypes"
 	"github.com/hyperledger/firefly/mocks/contractmocks"
 	"github.com/hyperledger/firefly/pkg/core"
 	"github.com/stretchr/testify/assert"
@@ -40,7 +41,7 @@ func TestGetContractAPIInterface(t *testing.T) {
 	res := httptest.NewRecorder()
 
 	mcm.On("GetContractAPIInterface", mock.Anything, "banana").
-		Return(&core.FFI{}, nil)
+		Return(&fftypes.FFI{}, nil)
 	r.ServeHTTP(res, req)
 
 	assert.Equal(t, 200, res.Result().StatusCode)
diff --git a/internal/apiserver/route_get_contract_interface_name_version_test.go b/internal/apiserver/route_get_contract_interface_name_version_test.go
index fbd4de2af..855279c8c 100644
--- a/internal/apiserver/route_get_contract_interface_name_version_test.go
+++ b/internal/apiserver/route_get_contract_interface_name_version_test.go
@@ -22,6 +22,7 @@ import (
 	"net/http/httptest"
 	"testing"
 
+	"github.com/hyperledger/firefly-common/pkg/fftypes"
 	"github.com/hyperledger/firefly/mocks/contractmocks"
 	"github.com/hyperledger/firefly/pkg/core"
 	"github.com/stretchr/testify/assert"
@@ -40,7 +41,7 @@ func TestGetContractInterfaceNameVersion(t *testing.T) {
 	res := httptest.NewRecorder()
 
 	mcm.On("GetFFI", mock.Anything, "banana", "v1.0.0").
-		Return(&core.FFI{}, nil)
+		Return(&fftypes.FFI{}, nil)
 	r.ServeHTTP(res, req)
 
 	assert.Equal(t, 200, res.Result().StatusCode)
@@ -58,7 +59,7 @@ func TestGetContractInterfaceNameVersionWithChildren(t *testing.T) {
 	res := httptest.NewRecorder()
 
 	mcm.On("GetFFIWithChildren", mock.Anything, "banana", "v1.0.0").
-		Return(&core.FFI{}, nil)
+		Return(&fftypes.FFI{}, nil)
 	r.ServeHTTP(res, req)
 
 	assert.Equal(t, 200, res.Result().StatusCode)
diff --git a/internal/apiserver/route_get_contract_interface_test.go b/internal/apiserver/route_get_contract_interface_test.go
index c98dd829f..b1bd18b5b 100644
--- a/internal/apiserver/route_get_contract_interface_test.go
+++ b/internal/apiserver/route_get_contract_interface_test.go
@@ -56,7 +56,7 @@ func TestGetContractInterface(t *testing.T) {
 	res := httptest.NewRecorder()
 
 	mcm.On("GetFFIByID", mock.Anything, id).
-		Return(&core.FFI{}, nil)
+		Return(&fftypes.FFI{}, nil)
 	r.ServeHTTP(res, req)
 
 	assert.Equal(t, 200, res.Result().StatusCode)
@@ -75,7 +75,7 @@ func TestGetContractInterfaceWithChildren(t *testing.T) {
 	res := httptest.NewRecorder()
 
 	mcm.On("GetFFIByIDWithChildren", mock.Anything, id).
-		Return(&core.FFI{}, nil)
+		Return(&fftypes.FFI{}, nil)
 	r.ServeHTTP(res, req)
 
 	assert.Equal(t, 200, res.Result().StatusCode)
diff --git a/internal/apiserver/route_get_contract_interfaces_test.go b/internal/apiserver/route_get_contract_interfaces_test.go
index cb37c93cf..6be1caec0 100644
--- a/internal/apiserver/route_get_contract_interfaces_test.go
+++ b/internal/apiserver/route_get_contract_interfaces_test.go
@@ -22,6 +22,7 @@ import (
 	"net/http/httptest"
 	"testing"
 
+	"github.com/hyperledger/firefly-common/pkg/fftypes"
 	"github.com/hyperledger/firefly/mocks/contractmocks"
 	"github.com/hyperledger/firefly/pkg/core"
 	"github.com/stretchr/testify/assert"
@@ -40,7 +41,7 @@ func TestGetContractInterfaces(t *testing.T) {
 	res := httptest.NewRecorder()
 
 	mcm.On("GetFFIs", mock.Anything, mock.Anything).
-		Return([]*core.FFI{}, nil, nil)
+		Return([]*fftypes.FFI{}, nil, nil)
 	r.ServeHTTP(res, req)
 
 	assert.Equal(t, 200, res.Result().StatusCode)
diff --git a/internal/apiserver/route_post_contract_interface_generate_test.go b/internal/apiserver/route_post_contract_interface_generate_test.go
index e0329ddec..1a08ccc64 100644
--- a/internal/apiserver/route_post_contract_interface_generate_test.go
+++ b/internal/apiserver/route_post_contract_interface_generate_test.go
@@ -22,6 +22,7 @@ import (
 	"net/http/httptest"
 	"testing"
 
+	"github.com/hyperledger/firefly-common/pkg/fftypes"
 	"github.com/hyperledger/firefly/mocks/contractmocks"
 	"github.com/hyperledger/firefly/pkg/core"
 	"github.com/stretchr/testify/assert"
@@ -40,7 +41,7 @@ func TestPostContractInterfaceGenerate(t *testing.T) {
 	res := httptest.NewRecorder()
 
 	mcm.On("GenerateFFI", mock.Anything, mock.Anything).
-		Return(&core.FFI{}, nil)
+		Return(&fftypes.FFI{}, nil)
 	r.ServeHTTP(res, req)
 
 	assert.Equal(t, 200, res.Result().StatusCode)
diff --git a/internal/apiserver/route_post_new_contract_interface_test.go b/internal/apiserver/route_post_new_contract_interface_test.go
index 3d3d680fc..8b7bba56d 100644
--- a/internal/apiserver/route_post_new_contract_interface_test.go
+++ b/internal/apiserver/route_post_new_contract_interface_test.go
@@ -39,7 +39,7 @@ func TestPostNewContractInterface(t *testing.T) {
 	req.Header.Set("Content-Type", "application/json; charset=utf-8")
 	res := httptest.NewRecorder()
 
-	mds.On("DefineFFI", mock.Anything, mock.AnythingOfType("*core.FFI"), false).Return(nil)
+	mds.On("DefineFFI", mock.Anything, mock.AnythingOfType("*fftypes.FFI"), false).Return(nil)
 	r.ServeHTTP(res, req)
 
 	assert.Equal(t, 202, res.Result().StatusCode)
@@ -56,7 +56,7 @@ func TestPostNewContractInterfaceSync(t *testing.T) {
 	req.Header.Set("Content-Type", "application/json; charset=utf-8")
 	res := httptest.NewRecorder()
 
-	mds.On("DefineFFI", mock.Anything, mock.AnythingOfType("*core.FFI"), true).Return(nil)
+	mds.On("DefineFFI", mock.Anything, mock.AnythingOfType("*fftypes.FFI"), true).Return(nil)
 	r.ServeHTTP(res, req)
 
 	assert.Equal(t, 200, res.Result().StatusCode)
diff --git a/internal/apiserver/server_test.go b/internal/apiserver/server_test.go
index 74c4d73a7..0218b5ca8 100644
--- a/internal/apiserver/server_test.go
+++ b/internal/apiserver/server_test.go
@@ -258,9 +258,9 @@ func TestContractAPISwaggerJSON(t *testing.T) {
 	s := httptest.NewServer(r)
 	defer s.Close()
 
-	ffi := &core.FFI{}
+	ffi := &fftypes.FFI{}
 	api := &core.ContractAPI{
-		Interface: &core.FFIReference{
+		Interface: &fftypes.FFIReference{
 			ID: fftypes.NewUUID(),
 		},
 	}
@@ -313,7 +313,7 @@ func TestContractAPISwaggerJSONGetFFIFail(t *testing.T) {
 	defer s.Close()
 
 	api := &core.ContractAPI{
-		Interface: &core.FFIReference{
+		Interface: &fftypes.FFIReference{
 			ID: fftypes.NewUUID(),
 		},
 	}
diff --git a/internal/blockchain/ethereum/ethereum_test.go b/internal/blockchain/ethereum/ethereum_test.go
index 86f5629a7..ff491170c 100644
--- a/internal/blockchain/ethereum/ethereum_test.go
+++ b/internal/blockchain/ethereum/ethereum_test.go
@@ -28,12 +28,9 @@ 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"
@@ -49,10 +46,10 @@ var utEthconnectConf = utConfig.SubSection(EthconnectConfigKey)
 var utAddressResolverConf = utConfig.SubSection(AddressResolverConfigKey)
 var utFFTMConf = utConfig.SubSection(FFTMConfigKey)
 
-func testFFIMethod() *core.FFIMethod {
-	return &core.FFIMethod{
+func testFFIMethod() *fftypes.FFIMethod {
+	return &fftypes.FFIMethod{
 		Name: "sum",
-		Params: []*core.FFIParam{
+		Params: []*fftypes.FFIParam{
 			{
 				Name:   "x",
 				Schema: fftypes.JSONAnyPtr(`{"oneOf":[{"type":"string"},{"type":"integer"}],"details":{"type":"uint256"}}`),
@@ -62,7 +59,7 @@ func testFFIMethod() *core.FFIMethod {
 				Schema: fftypes.JSONAnyPtr(`{"oneOf":[{"type":"string"},{"type":"integer"}],"details":{"type":"uint256"}}`),
 			},
 		},
-		Returns: []*core.FFIParam{
+		Returns: []*fftypes.FFIParam{
 			{
 				Name:   "z",
 				Schema: fftypes.JSONAnyPtr(`{"oneOf":[{"type":"string"},{"type":"integer"}],"details":{"type":"uint256"}}`),
@@ -1573,10 +1570,10 @@ func TestFormatNil(t *testing.T) {
 	assert.Equal(t, "0x0000000000000000000000000000000000000000000000000000000000000000", ethHexFormatB32(nil))
 }
 
-func encodeDetails(internalType string) *fftypes.JSONAny {
-	result, _ := json.Marshal(&paramDetails{Type: internalType})
-	return fftypes.JSONAnyPtrBytes(result)
-}
+// func encodeDetails(internalType string) *fftypes.JSONAny {
+// 	result, _ := json.Marshal(&paramDetails{Type: internalType})
+// 	return fftypes.JSONAnyPtrBytes(result)
+// }
 
 func TestAddSubscription(t *testing.T) {
 	e, cancel := newTestEthereum()
@@ -1594,9 +1591,9 @@ func TestAddSubscription(t *testing.T) {
 				"address": "0x123",
 			}.String()),
 			Event: &core.FFISerializedEvent{
-				FFIEventDefinition: core.FFIEventDefinition{
+				FFIEventDefinition: fftypes.FFIEventDefinition{
 					Name: "Changed",
-					Params: core.FFIParams{
+					Params: fftypes.FFIParams{
 						{
 							Name:   "value",
 							Schema: fftypes.JSONAnyPtr(`{"type": "string", "details": {"type": "string"}}`),
@@ -1634,9 +1631,9 @@ func TestAddSubscriptionBadParamDetails(t *testing.T) {
 				"address": "0x123",
 			}.String()),
 			Event: &core.FFISerializedEvent{
-				FFIEventDefinition: core.FFIEventDefinition{
+				FFIEventDefinition: fftypes.FFIEventDefinition{
 					Name: "Changed",
-					Params: core.FFIParams{
+					Params: fftypes.FFIParams{
 						{
 							Name:   "value",
 							Schema: fftypes.JSONAnyPtr(`{"type": "string", "details": {"type": ""}}`),
@@ -2017,9 +2014,9 @@ func TestInvokeContractPrepareFail(t *testing.T) {
 	location := &Location{
 		Address: "0x12345",
 	}
-	method := &core.FFIMethod{
+	method := &fftypes.FFIMethod{
 		Name: "set",
-		Params: core.FFIParams{
+		Params: fftypes.FFIParams{
 			{
 				Schema: fftypes.JSONAnyPtr("{bad schema!"),
 			},
@@ -2102,8 +2099,8 @@ func TestQueryContractErrorPrepare(t *testing.T) {
 	location := &Location{
 		Address: "0x12345",
 	}
-	method := &core.FFIMethod{
-		Params: core.FFIParams{
+	method := &fftypes.FFIMethod{
+		Params: fftypes.FFIParams{
 			{
 				Name:   "bad",
 				Schema: fftypes.JSONAnyPtr("{badschema}"),
@@ -2246,660 +2243,9 @@ func TestGetContractAddressBadJSON(t *testing.T) {
 	assert.Regexp(t, "invalid character 'n' looking for beginning of object key string", err)
 }
 
-func TestGetContractAddressBadResponse(t *testing.T) {
-	e, cancel := newTestEthereum()
-	defer cancel()
-
-	mockedClient := &http.Client{}
-	httpmock.ActivateNonDefault(mockedClient)
-	defer httpmock.DeactivateAndReset()
-
-	httpmock.RegisterResponder("GET", "http://localhost:12345/contracts/firefly",
-		httpmock.NewJsonResponderOrPanic(500, ethError{Error: "FFEC100148"}))
-
-	resetConf(e)
-	utEthconnectConf.Set(ffresty.HTTPConfigURL, "http://localhost:12345")
-	utEthconnectConf.Set(ffresty.HTTPCustomClient, mockedClient)
-	utEthconnectConf.Set(EthconnectConfigInstanceDeprecated, "0x12345")
-	utEthconnectConf.Set(EthconnectConfigTopic, "topic1")
-
-	e.client = ffresty.New(e.ctx, utEthconnectConf)
-
-	_, err := e.getContractAddress(context.Background(), "/contracts/firefly")
-
-	assert.Regexp(t, "FF10111", err)
-}
-
-func TestFFIMethodToABI(t *testing.T) {
-	e, _ := newTestEthereum()
-
-	method := &core.FFIMethod{
-		Name: "set",
-		Params: []*core.FFIParam{
-			{
-				Name: "newValue",
-				Schema: fftypes.JSONAnyPtr(`{
-					"type": "integer",
-					"details": {
-						"type": "uint256"
-					}
-				}`),
-			},
-		},
-		Returns: []*core.FFIParam{},
-	}
-
-	expectedABIElement := &abi.Entry{
-		Name: "set",
-		Type: "function",
-		Inputs: abi.ParameterArray{
-			{
-				Name:    "newValue",
-				Type:    "uint256",
-				Indexed: false,
-			},
-		},
-		Outputs: abi.ParameterArray{},
-	}
-
-	abi, err := e.FFIMethodToABI(context.Background(), method, nil)
-	assert.NoError(t, err)
-	assert.Equal(t, expectedABIElement, abi)
-}
-
-func TestFFIMethodToABIObject(t *testing.T) {
-	e, _ := newTestEthereum()
-
-	method := &core.FFIMethod{
-		Name: "set",
-		Params: []*core.FFIParam{
-			{
-				Name: "widget",
-				Schema: fftypes.JSONAnyPtr(`{
-					"type": "object",
-					"details": {
-						"type": "tuple"
-					},
-					"properties": {
-						"radius": {
-							"type": "integer",
-							"details": {
-								"type": "uint256",
-								"index": 0,
-								"indexed": true
-							}
-						},
-						"numbers": {
-							"type": "array",
-							"details": {
-								"type": "uint256[]",
-								"index": 1
-							},
-							"items": {
-								"type": "integer",
-								"details": {
-									"type": "uint256"
-								}
-							}
-						}
-					}
-				}`),
-			},
-		},
-		Returns: []*core.FFIParam{},
-	}
-
-	expectedABIElement := abi.Entry{
-		Name: "set",
-		Type: "function",
-		Inputs: abi.ParameterArray{
-			{
-				Name:    "widget",
-				Type:    "tuple",
-				Indexed: false,
-				Components: abi.ParameterArray{
-					{
-						Name:         "radius",
-						Type:         "uint256",
-						Indexed:      true,
-						InternalType: "",
-					},
-					{
-						Name:         "numbers",
-						Type:         "uint256[]",
-						Indexed:      false,
-						InternalType: "",
-					},
-				},
-			},
-		},
-		Outputs: abi.ParameterArray{},
-	}
-
-	abi, err := e.FFIMethodToABI(context.Background(), method, nil)
-	assert.NoError(t, err)
-	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) {
-	e, _ := newTestEthereum()
-
-	method := &core.FFIMethod{
-		Name: "set",
-		Params: []*core.FFIParam{
-			{
-				Name: "widget",
-				Schema: fftypes.JSONAnyPtr(`{
-					"type": "array",
-					"details": {
-						"type": "string[][]",
-						"internalType": "string[][]"
-					},
-					"items": {
-						"type": "array",
-						"items": {
-							"type": "string"
-						}
-					}
-				}`),
-			},
-		},
-		Returns: []*core.FFIParam{},
-	}
-
-	expectedABIElement := &abi.Entry{
-		Name: "set",
-		Type: "function",
-		Inputs: abi.ParameterArray{
-			{
-				Name:         "widget",
-				Type:         "string[][]",
-				InternalType: "string[][]",
-				Indexed:      false,
-			},
-		},
-		Outputs: abi.ParameterArray{},
-	}
-
-	abi, err := e.FFIMethodToABI(context.Background(), method, nil)
-	assert.NoError(t, err)
-	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) {
-	e, _ := newTestEthereum()
-
-	method := &core.FFIMethod{
-		Name: "set",
-		Params: []*core.FFIParam{
-			{
-				Name:   "newValue",
-				Schema: fftypes.JSONAnyPtr(`{#!`),
-			},
-		},
-		Returns: []*core.FFIParam{},
-	}
-
-	_, err := e.FFIMethodToABI(context.Background(), method, nil)
-	assert.Regexp(t, "invalid json", err)
-}
-
-func TestFFIMethodToABIBadSchema(t *testing.T) {
-	e, _ := newTestEthereum()
-
-	method := &core.FFIMethod{
-		Name: "set",
-		Params: []*core.FFIParam{
-			{
-				Name: "newValue",
-				Schema: fftypes.JSONAnyPtr(`{
-					"type": "integer",
-					"detailz": {
-						"type": "uint256"
-					}
-				}`),
-			},
-		},
-		Returns: []*core.FFIParam{},
-	}
-
-	_, err := e.FFIMethodToABI(context.Background(), method, nil)
-	assert.Regexp(t, "compilation failed", err)
-}
-
-func TestFFIMethodToABIBadReturn(t *testing.T) {
-	e, _ := newTestEthereum()
-
-	method := &core.FFIMethod{
-		Name:   "set",
-		Params: []*core.FFIParam{},
-		Returns: []*core.FFIParam{
-			{
-				Name: "newValue",
-				Schema: fftypes.JSONAnyPtr(`{
-					"type": "integer",
-					"detailz": {
-						"type": "uint256"
-					}
-				}`),
-			},
-		},
-	}
-
-	_, err := e.FFIMethodToABI(context.Background(), method, nil)
-	assert.Regexp(t, "compilation failed", err)
-}
-
-func TestConvertABIToFFI(t *testing.T) {
-	e, _ := newTestEthereum()
-
-	abi := &abi.ABI{
-		{
-			Name: "set",
-			Type: "function",
-			Inputs: abi.ParameterArray{
-				{
-					Name:         "newValue",
-					Type:         "uint256",
-					InternalType: "uint256",
-				},
-			},
-			Outputs: abi.ParameterArray{},
-		},
-		{
-			Name:   "get",
-			Type:   "function",
-			Inputs: abi.ParameterArray{},
-			Outputs: abi.ParameterArray{
-				{
-					Name:         "value",
-					Type:         "uint256",
-					InternalType: "uint256",
-				},
-			},
-		},
-		{
-			Name: "Updated",
-			Type: "event",
-			Inputs: abi.ParameterArray{{
-				Name:         "value",
-				Type:         "uint256",
-				InternalType: "uint256",
-			}},
-			Outputs: abi.ParameterArray{},
-		},
-	}
-
-	schema := fftypes.JSONAnyPtr(`{"type":"integer","details":{"type":"uint256","internalType":"uint256"}}`)
-
-	expectedFFI := &core.FFI{
-		Name:        "SimpleStorage",
-		Version:     "v0.0.1",
-		Namespace:   "default",
-		Description: "desc",
-		Methods: []*core.FFIMethod{
-			{
-				Name: "set",
-				Params: core.FFIParams{
-					{
-						Name:   "newValue",
-						Schema: schema,
-					},
-				},
-				Returns: core.FFIParams{},
-			},
-			{
-				Name:   "get",
-				Params: core.FFIParams{},
-				Returns: core.FFIParams{
-					{
-						Name:   "value",
-						Schema: schema,
-					},
-				},
-			},
-		},
-		Events: []*core.FFIEvent{
-			{
-				FFIEventDefinition: core.FFIEventDefinition{
-					Name: "Updated",
-					Params: core.FFIParams{
-						{
-							Name:   "value",
-							Schema: schema,
-						},
-					},
-				},
-			},
-		},
-	}
-
-	actualFFI, err := e.convertABIToFFI(context.Background(), "default", "SimpleStorage", "v0.0.1", "desc", abi)
-	assert.NoError(t, err)
-	assert.NotNil(t, actualFFI)
-	assert.ObjectsAreEqual(expectedFFI, actualFFI)
-}
-
-func TestConvertABIToFFIWithObject(t *testing.T) {
-	e, _ := newTestEthereum()
-
-	abi := &abi.ABI{
-		&abi.Entry{
-			Name: "set",
-			Type: "function",
-			Inputs: abi.ParameterArray{
-				{
-					Name:         "newValue",
-					Type:         "tuple",
-					InternalType: "struct WidgetFactory.Widget",
-					Components: abi.ParameterArray{
-						{
-							Name:         "size",
-							Type:         "uint256",
-							InternalType: "uint256",
-						},
-						{
-							Name:         "description",
-							Type:         "string",
-							InternalType: "string",
-						},
-					},
-				},
-			},
-			Outputs: abi.ParameterArray{},
-		},
-	}
-
-	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",
-		Version:     "v0.0.1",
-		Namespace:   "default",
-		Description: "desc",
-		Methods: []*core.FFIMethod{
-			{
-				Name: "set",
-				Params: core.FFIParams{
-					{
-						Name:   "newValue",
-						Schema: schema,
-					},
-				},
-				Returns: core.FFIParams{},
-				Details: map[string]interface{}{},
-			},
-		},
-		Events: []*core.FFIEvent{},
-	}
-
-	actualFFI, err := e.convertABIToFFI(context.Background(), "default", "WidgetTest", "v0.0.1", "desc", abi)
-	assert.NoError(t, err)
-	assert.NotNil(t, 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 := &abi.ABI{
-		{
-			Name: "set",
-			Type: "function",
-			Inputs: abi.ParameterArray{
-				{
-					Name:         "newValue",
-					Type:         "string[]",
-					InternalType: "string[]",
-				},
-			},
-			Outputs: abi.ParameterArray{},
-		},
-	}
-
-	schema := fftypes.JSONAnyPtr(`{"type":"array","details":{"type":"string[]","internalType":"string[]"},"items":{"type":"string"}}`)
-
-	expectedFFI := &core.FFI{
-		Name:        "WidgetTest",
-		Version:     "v0.0.1",
-		Namespace:   "default",
-		Description: "desc",
-		Methods: []*core.FFIMethod{
-			{
-				Name: "set",
-				Params: core.FFIParams{
-					{
-						Name:   "newValue",
-						Schema: schema,
-					},
-				},
-				Returns: core.FFIParams{},
-				Details: map[string]interface{}{},
-			},
-		},
-		Events: []*core.FFIEvent{},
-	}
-
-	actualFFI, err := e.convertABIToFFI(context.Background(), "default", "WidgetTest", "v0.0.1", "desc", abi)
-	assert.NoError(t, err)
-	assert.NotNil(t, 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 := &abi.ABI{
-		{
-			Name: "set",
-			Type: "function",
-			Inputs: abi.ParameterArray{
-				{
-					Name:         "newValue",
-					Type:         "uint256[][]",
-					InternalType: "uint256[][]",
-				},
-			},
-			Outputs: abi.ParameterArray{},
-		},
-	}
-
-	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",
-		Namespace:   "default",
-		Description: "desc",
-		Methods: []*core.FFIMethod{
-			{
-				Name: "set",
-				Params: core.FFIParams{
-					{
-						Name:   "newValue",
-						Schema: schema,
-					},
-				},
-				Returns: core.FFIParams{},
-				Details: map[string]interface{}{},
-			},
-		},
-		Events: []*core.FFIEvent{},
-	}
-
-	actualFFI, err := e.convertABIToFFI(context.Background(), "default", "WidgetTest", "v0.0.1", "desc", abi)
-	assert.NoError(t, err)
-	assert.NotNil(t, 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 := &abi.ABI{
-		{
-			Name: "set",
-			Type: "function",
-			Inputs: abi.ParameterArray{
-				{
-					InternalType: "struct WidgetFactory.Widget[][]",
-					Name:         "gears",
-					Type:         "tuple[][]",
-					Components: abi.ParameterArray{
-						{
-							InternalType: "string",
-							Name:         "description",
-							Type:         "string",
-						},
-						{
-							InternalType: "uint256",
-							Name:         "size",
-							Type:         "uint256",
-						},
-						{
-							InternalType: "bool",
-							Name:         "inUse",
-							Type:         "bool",
-						},
-					},
-				},
-			},
-			Outputs: abi.ParameterArray{},
-		},
-	}
-
-	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",
-		Namespace:   "default",
-		Description: "desc",
-		Methods: []*core.FFIMethod{
-			{
-				Name: "set",
-				Params: core.FFIParams{
-					{
-						Name:   "gears",
-						Schema: schema,
-					},
-				},
-				Returns: core.FFIParams{},
-				Details: map[string]interface{}{},
-			},
-		},
-		Events: []*core.FFIEvent{},
-	}
-
-	actualFFI, err := e.convertABIToFFI(context.Background(), "default", "WidgetTest", "v0.0.1", "desc", abi)
-	assert.NoError(t, err)
-	assert.NotNil(t, 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) {
 	e, _ := newTestEthereum()
-	_, err := e.GenerateFFI(context.Background(), &core.FFIGenerationRequest{
+	_, err := e.GenerateFFI(context.Background(), &fftypes.FFIGenerationRequest{
 		Name:        "Simple",
 		Version:     "v0.0.1",
 		Description: "desc",
@@ -2910,7 +2256,7 @@ func TestGenerateFFI(t *testing.T) {
 
 func TestGenerateFFIInlineNamespace(t *testing.T) {
 	e, _ := newTestEthereum()
-	ffi, err := e.GenerateFFI(context.Background(), &core.FFIGenerationRequest{
+	ffi, err := e.GenerateFFI(context.Background(), &fftypes.FFIGenerationRequest{
 		Name:        "Simple",
 		Version:     "v0.0.1",
 		Description: "desc",
@@ -2923,7 +2269,7 @@ func TestGenerateFFIInlineNamespace(t *testing.T) {
 
 func TestGenerateFFIEmptyABI(t *testing.T) {
 	e, _ := newTestEthereum()
-	_, err := e.GenerateFFI(context.Background(), &core.FFIGenerationRequest{
+	_, err := e.GenerateFFI(context.Background(), &fftypes.FFIGenerationRequest{
 		Name:        "Simple",
 		Version:     "v0.0.1",
 		Description: "desc",
@@ -2934,7 +2280,7 @@ func TestGenerateFFIEmptyABI(t *testing.T) {
 
 func TestGenerateFFIBadABI(t *testing.T) {
 	e, _ := newTestEthereum()
-	_, err := e.GenerateFFI(context.Background(), &core.FFIGenerationRequest{
+	_, err := e.GenerateFFI(context.Background(), &fftypes.FFIGenerationRequest{
 		Name:        "Simple",
 		Version:     "v0.0.1",
 		Description: "desc",
@@ -2943,17 +2289,6 @@ func TestGenerateFFIBadABI(t *testing.T) {
 	assert.Regexp(t, "FF10346", err)
 }
 
-func TestGetFFIType(t *testing.T) {
-	e, _ := newTestEthereum()
-	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) {
 	e, _ := newTestEthereum()
 	complexParam := fftypes.JSONObject{
@@ -2979,9 +2314,9 @@ func TestGenerateEventSignature(t *testing.T) {
 		},
 	}.String()
 
-	event := &core.FFIEventDefinition{
+	event := &fftypes.FFIEventDefinition{
 		Name: "Changed",
-		Params: []*core.FFIParam{
+		Params: []*fftypes.FFIParam{
 			{
 				Name:   "x",
 				Schema: fftypes.JSONAnyPtr(`{"type": "integer", "details": {"type": "uint256"}}`),
@@ -3003,9 +2338,9 @@ func TestGenerateEventSignature(t *testing.T) {
 
 func TestGenerateEventSignatureInvalid(t *testing.T) {
 	e, _ := newTestEthereum()
-	event := &core.FFIEventDefinition{
+	event := &fftypes.FFIEventDefinition{
 		Name: "Changed",
-		Params: []*core.FFIParam{
+		Params: []*fftypes.FFIParam{
 			{
 				Name:   "x",
 				Schema: fftypes.JSONAnyPtr(`{"!bad": "bad"`),
@@ -3216,112 +2551,11 @@ func TestHandleNetworkActionFail(t *testing.T) {
 	em.AssertExpectations(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) {
+func TestGetFFIParamValidator(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)
+	v, err := e.GetFFIParamValidator(context.Background())
 	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))
+	assert.NotNil(t, v)
 }
 
 func TestGetNetworkVersion(t *testing.T) {
diff --git a/internal/blockchain/ethereum/ffi_param_validator_test.go b/internal/blockchain/ethereum/ffi_param_validator_test.go
deleted file mode 100644
index 4f740b334..000000000
--- a/internal/blockchain/ethereum/ffi_param_validator_test.go
+++ /dev/null
@@ -1,358 +0,0 @@
-// Copyright © 2022 Kaleido, Inc.
-//
-// SPDX-License-Identifier: Apache-2.0
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package ethereum
-
-import (
-	"context"
-	"encoding/json"
-	"strings"
-	"testing"
-
-	"github.com/hyperledger/firefly/pkg/core"
-	"github.com/santhosh-tekuri/jsonschema/v5"
-	"github.com/stretchr/testify/assert"
-)
-
-func NewTestSchema(input string) (*jsonschema.Schema, error) {
-	c := jsonschema.NewCompiler()
-	c.Draft = jsonschema.Draft2020
-	f := core.BaseFFIParamValidator{}
-	c.RegisterExtension(f.GetExtensionName(), f.GetMetaSchema(), f)
-	e, _ := newTestEthereum()
-	v, err := e.GetFFIParamValidator(context.Background())
-	if err != nil {
-		return nil, err
-	}
-	c.RegisterExtension(v.GetExtensionName(), v.GetMetaSchema(), v)
-	err = c.AddResource("schema.json", strings.NewReader(input))
-	if err != nil {
-		return nil, err
-	}
-	return c.Compile("schema.json")
-}
-
-func jsonDecode(input string) interface{} {
-	var output interface{}
-	json.Unmarshal([]byte(input), &output)
-	return output
-}
-
-func TestSchemaValid(t *testing.T) {
-	_, err := NewTestSchema(`
-{
-	"type": "integer",
-	"details": {
-		"type": "uint256"
-	}
-}`)
-	assert.NoError(t, err)
-}
-
-func TestSchemaValidBytes(t *testing.T) {
-	_, err := NewTestSchema(`
-{
-	"type": "string",
-	"details": {
-		"type": "bytes"
-	}
-}`)
-	assert.NoError(t, err)
-}
-
-func TestSchemaValidBytes32(t *testing.T) {
-	_, err := NewTestSchema(`
-{
-	"type": "string",
-	"details": {
-		"type": "bytes32"
-	}
-}`)
-	assert.NoError(t, err)
-}
-
-func TestSchemaTypeInvalid(t *testing.T) {
-	_, err := NewTestSchema(`
-{
-	"type": "foobar",
-	"details": {
-		"type": "uint256"
-	}
-}`)
-	assert.Regexp(t, "'/type' does not validate", err)
-}
-
-func TestSchemaTypeInvalidFFIType(t *testing.T) {
-	_, err := NewTestSchema(`
-{
-	"type": "number",
-	"details": {
-		"type": "uint256"
-	}
-}`)
-	assert.Regexp(t, "oneOf failed", err)
-}
-
-func TestSchemaTypeMissing(t *testing.T) {
-	_, err := NewTestSchema(`{}`)
-	assert.Regexp(t, "compilation failed", err)
-}
-
-func TestSchemaDetailsTypeMissing(t *testing.T) {
-	_, err := NewTestSchema(`
-{
-	"type": "string",
-	"details": {
-		"indexed": true
-	}
-}`)
-	assert.Regexp(t, "compilation failed", err)
-}
-
-func TestSchemaDetailsIndexedWrongType(t *testing.T) {
-	_, err := NewTestSchema(`
-{
-	"type": "string",
-	"details": {
-		"type": "string",
-		"indexed": "string"
-	}
-}`)
-	assert.Regexp(t, "compilation failed", err)
-}
-
-func TestSchemaTypeMismatch(t *testing.T) {
-	_, err := NewTestSchema(`
-{
-	"type": "string",
-	"details": {
-		"type": "boolean"
-	}
-}`)
-	assert.Regexp(t, "cannot cast string to boolean", err)
-}
-
-func TestSchemaTypeMismatchArray(t *testing.T) {
-	_, err := NewTestSchema(`
-{
-	"type": "array",
-	"details": {
-		"type": "string"
-	}
-}`)
-	assert.Regexp(t, "cannot cast array to string", err)
-}
-
-func TestSchemaTypeMismatchObject(t *testing.T) {
-	_, err := NewTestSchema(`
-{
-	"type": "object",
-	"details": {
-		"type": "string"
-	}
-}`)
-	assert.Regexp(t, "cannot cast object to string", err)
-}
-
-func TestInputString(t *testing.T) {
-	s, err := NewTestSchema(`
-{
-	"type": "string",
-	"details": {
-		"type": "string"
-	}
-}`)
-	assert.NoError(t, err)
-	err = s.Validate(`"banana"`)
-	assert.NoError(t, err)
-}
-
-func TestInputInteger(t *testing.T) {
-	s, err := NewTestSchema(`
-{
-	"type": "integer",
-	"details": {
-		"type": "uint256"
-	}
-}`)
-	assert.NoError(t, err)
-	err = s.Validate(1)
-	assert.NoError(t, err)
-}
-
-func TestInputBoolean(t *testing.T) {
-	s, err := NewTestSchema(`
-{
-	"type": "boolean",
-	"details": {
-		"type": "bool"
-	}
-}`)
-	assert.NoError(t, err)
-	err = s.Validate(true)
-	assert.NoError(t, err)
-}
-
-func TestInputStruct(t *testing.T) {
-	s, err := NewTestSchema(`
-{
-	"type": "object",
-	"details": {
-		"type": "tuple"
-	},
-	"properties": {
-		"x": {
-			"type": "integer",
-			"details": {
-				"type": "uint8",
-				"index": 0
-			}
-		},
-		"y": {
-			"type": "integer",
-			"details": {
-				"type": "uint8",
-				"index": 1
-			}
-		},
-		"z": {
-			"type": "integer",
-			"details": {
-				"type": "uint8",
-				"index": 2
-			}
-		}
-	},
-	"required": ["x", "y", "z"]
-}`)
-
-	input := `{
-	"x": 123,
-	"y": 456,
-	"z": 789		
-}`
-
-	assert.NoError(t, err)
-	err = s.Validate(jsonDecode(input))
-	assert.NoError(t, err)
-}
-
-func TestInputArray(t *testing.T) {
-	s, err := NewTestSchema(`
-{
-	"type": "array",
-	"details": {
-		"type": "uint8[]"
-	},
-	"items": {
-		"type": "integer"
-	}
-}`)
-
-	input := `[123,456,789]`
-
-	assert.NoError(t, err)
-	err = s.Validate(jsonDecode(input))
-	assert.NoError(t, err)
-}
-
-func TestInputInvalidBlockchainType(t *testing.T) {
-	_, err := NewTestSchema(`
-{
-	"type": "boolean",
-	"details": {
-		"type": "foobar"
-	}
-}`)
-	assert.Regexp(t, "cannot cast boolean to foobar", err)
-}
-
-func TestInputInvalidNestedBlockchainType(t *testing.T) {
-	_, err := NewTestSchema(`
-{
-	"type": "object",
-	"details": {
-		"type": "tuple"
-	},
-	"properties": {
-		"amount": {
-			"type": "integer",
-			"details": {
-				"type": "string",
-				"index": 0
-			}
-		}
-	}
-}`)
-	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(`
-{
-	"type": "object",
-	"details": {
-		"type": "tuple"
-	},
-	"properties": {
-		"foo": {
-			"type": "string",
-			"details": {
-				"type": "string",
-				"index": 0
-			}
-		}
-	},
-	"additionalProperties": false
-}`)
-
-	input := `{
-	"foo": "foo",
-	"bar": "bar"		
-}`
-
-	assert.NoError(t, err)
-	err = s.Validate(jsonDecode(input))
-	assert.Regexp(t, "additionalProperties 'bar' not allowed", err)
-}
diff --git a/internal/blockchain/fabric/fabric_test.go b/internal/blockchain/fabric/fabric_test.go
index 903b87e20..c099f50e7 100644
--- a/internal/blockchain/fabric/fabric_test.go
+++ b/internal/blockchain/fabric/fabric_test.go
@@ -73,10 +73,10 @@ func newTestFabric() (*Fabric, func()) {
 	}
 }
 
-func testFFIMethod() *core.FFIMethod {
-	return &core.FFIMethod{
+func testFFIMethod() *fftypes.FFIMethod {
+	return &fftypes.FFIMethod{
 		Name: "sum",
-		Params: []*core.FFIParam{
+		Params: []*fftypes.FFIParam{
 			{
 				Name:   "x",
 				Schema: fftypes.JSONAnyPtr(`{"type": "integer"}`),
@@ -90,7 +90,7 @@ func testFFIMethod() *core.FFIMethod {
 				Schema: fftypes.JSONAnyPtr(`{"type": "string"}`),
 			},
 		},
-		Returns: []*core.FFIParam{
+		Returns: []*fftypes.FFIParam{
 			{
 				Name:   "z",
 				Schema: fftypes.JSONAnyPtr(`{"type": "integer"}`),
@@ -1837,15 +1837,15 @@ func TestInvokeContractBadSchema(t *testing.T) {
 		Channel:   "firefly",
 		Chaincode: "simplestorage",
 	}
-	method := &core.FFIMethod{
+	method := &fftypes.FFIMethod{
 		Name: "sum",
-		Params: []*core.FFIParam{
+		Params: []*fftypes.FFIParam{
 			{
 				Name:   "x",
 				Schema: fftypes.JSONAnyPtr(`{not json]`),
 			},
 		},
-		Returns: []*core.FFIParam{},
+		Returns: []*fftypes.FFIParam{},
 	}
 	params := map[string]interface{}{
 		"x":           float64(1),
@@ -2116,7 +2116,7 @@ func TestGetFFIParamValidator(t *testing.T) {
 
 func TestGenerateFFI(t *testing.T) {
 	e, _ := newTestFabric()
-	_, err := e.GenerateFFI(context.Background(), &core.FFIGenerationRequest{
+	_, err := e.GenerateFFI(context.Background(), &fftypes.FFIGenerationRequest{
 		Name:        "Simple",
 		Version:     "v0.0.1",
 		Description: "desc",
@@ -2127,7 +2127,7 @@ func TestGenerateFFI(t *testing.T) {
 
 func TestGenerateEventSignature(t *testing.T) {
 	e, _ := newTestFabric()
-	signature := e.GenerateEventSignature(context.Background(), &core.FFIEventDefinition{Name: "Changed"})
+	signature := e.GenerateEventSignature(context.Background(), &fftypes.FFIEventDefinition{Name: "Changed"})
 	assert.Equal(t, "Changed", signature)
 }
 
diff --git a/internal/contracts/manager_test.go b/internal/contracts/manager_test.go
index 86037f931..1c03888eb 100644
--- a/internal/contracts/manager_test.go
+++ b/internal/contracts/manager_test.go
@@ -22,7 +22,7 @@ import (
 	"testing"
 
 	"github.com/hyperledger/firefly-common/pkg/fftypes"
-	"github.com/hyperledger/firefly/internal/blockchain/ethereum"
+	"github.com/hyperledger/firefly-signer/pkg/ffi2abi"
 	"github.com/hyperledger/firefly/internal/identity"
 	"github.com/hyperledger/firefly/internal/syncasync"
 	"github.com/hyperledger/firefly/internal/txcommon"
@@ -95,7 +95,7 @@ func TestNewContractManagerFFISchemaLoader(t *testing.T) {
 	mom := &operationmocks.Manager{}
 	txHelper := txcommon.NewTransactionHelper("ns1", mdi, mdm)
 	msa := &syncasyncmocks.Bridge{}
-	mbi.On("GetFFIParamValidator", mock.Anything).Return(&ethereum.FFIParamValidator{}, nil)
+	mbi.On("GetFFIParamValidator", mock.Anything).Return(&ffi2abi.ParamValidator{}, nil)
 	mom.On("RegisterHandler", mock.Anything, mock.Anything, mock.Anything)
 	_, err := NewContractManager(context.Background(), "ns1", mdi, mbi, mim, mom, txHelper, msa)
 	assert.NoError(t, err)
@@ -107,19 +107,19 @@ func TestResolveFFI(t *testing.T) {
 
 	mdb.On("GetFFI", mock.Anything, "ns1", "test", "1.0.0").Return(nil, nil)
 
-	ffi := &core.FFI{
+	ffi := &fftypes.FFI{
 		Namespace: "ns1",
 		Name:      "test",
 		Version:   "1.0.0",
 		ID:        fftypes.NewUUID(),
-		Methods: []*core.FFIMethod{
+		Methods: []*fftypes.FFIMethod{
 			{
 				Name: "sum",
 			},
 		},
-		Events: []*core.FFIEvent{
+		Events: []*fftypes.FFIEvent{
 			{
-				FFIEventDefinition: core.FFIEventDefinition{
+				FFIEventDefinition: fftypes.FFIEventDefinition{
 					Name: "changed",
 				},
 			},
@@ -136,18 +136,18 @@ func TestBroadcastFFIInvalid(t *testing.T) {
 
 	mdb.On("GetFFI", mock.Anything, "ns1", "test", "1.0.0").Return(nil, nil)
 
-	ffi := &core.FFI{
+	ffi := &fftypes.FFI{
 		Namespace: "ns1",
 		Name:      "test",
 		Version:   "1.0.0",
 		ID:        fftypes.NewUUID(),
-		Methods: []*core.FFIMethod{
+		Methods: []*fftypes.FFIMethod{
 			{
 				Name: "sum",
-				Params: []*core.FFIParam{
+				Params: []*fftypes.FFIParam{
 					{
 						Name:   "x",
-						Schema: fftypes.JSONAnyPtr(`{"type": "number"}`),
+						Schema: fftypes.JSONAnyPtr(`{"type": "null"}`),
 					},
 				},
 			},
@@ -164,21 +164,21 @@ func TestResolveFFIExists(t *testing.T) {
 	cm := newTestContractManager()
 	mdb := cm.database.(*databasemocks.Plugin)
 
-	mdb.On("GetFFI", mock.Anything, "ns1", "test", "1.0.0").Return(&core.FFI{}, nil)
+	mdb.On("GetFFI", mock.Anything, "ns1", "test", "1.0.0").Return(&fftypes.FFI{}, nil)
 
-	ffi := &core.FFI{
+	ffi := &fftypes.FFI{
 		Namespace: "ns1",
 		Name:      "test",
 		Version:   "1.0.0",
 		ID:        fftypes.NewUUID(),
-		Methods: []*core.FFIMethod{
+		Methods: []*fftypes.FFIMethod{
 			{
 				Name: "sum",
 			},
 		},
-		Events: []*core.FFIEvent{
+		Events: []*fftypes.FFIEvent{
 			{
-				FFIEventDefinition: core.FFIEventDefinition{
+				FFIEventDefinition: fftypes.FFIEventDefinition{
 					Name: "changed",
 				},
 			},
@@ -193,9 +193,9 @@ func TestValidateInvokeContractRequest(t *testing.T) {
 	cm := newTestContractManager()
 	req := &core.ContractCallRequest{
 		Type: core.CallTypeInvoke,
-		Method: &core.FFIMethod{
+		Method: &fftypes.FFIMethod{
 			Name: "sum",
-			Params: []*core.FFIParam{
+			Params: []*fftypes.FFIParam{
 				{
 					Name:   "x",
 					Schema: fftypes.JSONAnyPtr(`{"type": "integer", "details": {"type": "uint256"}}`),
@@ -205,7 +205,7 @@ func TestValidateInvokeContractRequest(t *testing.T) {
 					Schema: fftypes.JSONAnyPtr(`{"type": "integer", "details": {"type": "uint256"}}`),
 				},
 			},
-			Returns: []*core.FFIParam{
+			Returns: []*fftypes.FFIParam{
 				{
 					Name:   "z",
 					Schema: fftypes.JSONAnyPtr(`{"type": "integer", "details": {"type": "uint256"}}`),
@@ -225,9 +225,9 @@ func TestValidateInvokeContractRequestMissingInput(t *testing.T) {
 	cm := newTestContractManager()
 	req := &core.ContractCallRequest{
 		Type: core.CallTypeInvoke,
-		Method: &core.FFIMethod{
+		Method: &fftypes.FFIMethod{
 			Name: "sum",
-			Params: []*core.FFIParam{
+			Params: []*fftypes.FFIParam{
 				{
 					Name:   "x",
 					Schema: fftypes.JSONAnyPtr(`{"type": "integer", "details": {"type": "uint256"}}`),
@@ -237,7 +237,7 @@ func TestValidateInvokeContractRequestMissingInput(t *testing.T) {
 					Schema: fftypes.JSONAnyPtr(`{"type": "integer", "details": {"type": "uint256"}}`),
 				},
 			},
-			Returns: []*core.FFIParam{
+			Returns: []*fftypes.FFIParam{
 				{
 					Name:   "z",
 					Schema: fftypes.JSONAnyPtr(`{"type": "integer", "details": {"type": "uint256"}}`),
@@ -256,9 +256,9 @@ func TestValidateInvokeContractRequestInputWrongType(t *testing.T) {
 	cm := newTestContractManager()
 	req := &core.ContractCallRequest{
 		Type: core.CallTypeInvoke,
-		Method: &core.FFIMethod{
+		Method: &fftypes.FFIMethod{
 			Name: "sum",
-			Params: []*core.FFIParam{
+			Params: []*fftypes.FFIParam{
 				{
 					Name:   "x",
 					Schema: fftypes.JSONAnyPtr(`{"type": "integer", "details": {"type": "uint256"}}`),
@@ -268,7 +268,7 @@ func TestValidateInvokeContractRequestInputWrongType(t *testing.T) {
 					Schema: fftypes.JSONAnyPtr(`{"type": "integer", "details": {"type": "uint256"}}`),
 				},
 			},
-			Returns: []*core.FFIParam{
+			Returns: []*fftypes.FFIParam{
 				{
 					Name:   "z",
 					Schema: fftypes.JSONAnyPtr(`{"type": "integer", "details": {"type": "uint256"}}`),
@@ -288,9 +288,42 @@ func TestValidateInvokeContractRequestInvalidParam(t *testing.T) {
 	cm := newTestContractManager()
 	req := &core.ContractCallRequest{
 		Type: core.CallTypeInvoke,
-		Method: &core.FFIMethod{
+		Method: &fftypes.FFIMethod{
 			Name: "sum",
-			Params: []*core.FFIParam{
+			Params: []*fftypes.FFIParam{
+				{
+					Name:   "x",
+					Schema: fftypes.JSONAnyPtr(`{"type": "null", "details": {"type": "uint256"}}`),
+				},
+				{
+					Name:   "y",
+					Schema: fftypes.JSONAnyPtr(`{"type": "integer", "details": {"type": "uint256"}}`),
+				},
+			},
+			Returns: []*fftypes.FFIParam{
+				{
+					Name:   "z",
+					Schema: fftypes.JSONAnyPtr(`{"type": "integer"}`),
+				},
+			},
+		},
+		Input: map[string]interface{}{
+			"x": float64(1),
+			"y": float64(2),
+		},
+	}
+
+	err := cm.validateInvokeContractRequest(context.Background(), req)
+	assert.Regexp(t, "does not validate", err)
+}
+
+func TestValidateInvokeContractRequestInvalidReturn(t *testing.T) {
+	cm := newTestContractManager()
+	req := &core.ContractCallRequest{
+		Type: core.CallTypeInvoke,
+		Method: &fftypes.FFIMethod{
+			Name: "sum",
+			Params: []*fftypes.FFIParam{
 				{
 					Name:   "x",
 					Schema: fftypes.JSONAnyPtr(`{"type": "integer", "details": {"type": "uint256"}}`),
@@ -300,10 +333,10 @@ func TestValidateInvokeContractRequestInvalidParam(t *testing.T) {
 					Schema: fftypes.JSONAnyPtr(`{"type": "integer", "details": {"type": "uint256"}}`),
 				},
 			},
-			Returns: []*core.FFIParam{
+			Returns: []*fftypes.FFIParam{
 				{
 					Name:   "z",
-					Schema: fftypes.JSONAnyPtr(`{"type": "number"}`),
+					Schema: fftypes.JSONAnyPtr(`{"type": "null"}`),
 				},
 			},
 		},
@@ -321,14 +354,14 @@ func TestValidateFFI(t *testing.T) {
 	cm := newTestContractManager()
 	mdi := cm.database.(*databasemocks.Plugin)
 
-	ffi := &core.FFI{
+	ffi := &fftypes.FFI{
 		Name:      "math",
 		Version:   "1.0.0",
 		Namespace: "default",
-		Methods: []*core.FFIMethod{
+		Methods: []*fftypes.FFIMethod{
 			{
 				Name: "sum",
-				Params: []*core.FFIParam{
+				Params: []*fftypes.FFIParam{
 					{
 						Name:   "x",
 						Schema: fftypes.JSONAnyPtr(`{"type": "integer", "details": {"type": "uint256"}}`),
@@ -338,7 +371,7 @@ func TestValidateFFI(t *testing.T) {
 						Schema: fftypes.JSONAnyPtr(`{"type": "integer", "details": {"type": "uint256"}}`),
 					},
 				},
-				Returns: []*core.FFIParam{
+				Returns: []*fftypes.FFIParam{
 					{
 						Name:   "z",
 						Schema: fftypes.JSONAnyPtr(`{"type": "integer", "details": {"type": "uint256"}}`),
@@ -348,15 +381,15 @@ func TestValidateFFI(t *testing.T) {
 			{
 				Name:        "sum",
 				Description: "Override of sum method with different args",
-				Params:      []*core.FFIParam{},
-				Returns:     []*core.FFIParam{},
+				Params:      []*fftypes.FFIParam{},
+				Returns:     []*fftypes.FFIParam{},
 			},
 		},
-		Events: []*core.FFIEvent{
+		Events: []*fftypes.FFIEvent{
 			{
-				FFIEventDefinition: core.FFIEventDefinition{
+				FFIEventDefinition: fftypes.FFIEventDefinition{
 					Name: "sum",
-					Params: []*core.FFIParam{
+					Params: []*fftypes.FFIParam{
 						{
 							Name:   "z",
 							Schema: fftypes.JSONAnyPtr(`{"type": "integer", "details": {"type": "uint256"}}`),
@@ -365,10 +398,10 @@ func TestValidateFFI(t *testing.T) {
 				},
 			},
 			{
-				FFIEventDefinition: core.FFIEventDefinition{
+				FFIEventDefinition: fftypes.FFIEventDefinition{
 					Name:        "sum",
 					Description: "Override of event with different params",
-					Params:      []*core.FFIParam{},
+					Params:      []*fftypes.FFIParam{},
 				},
 			},
 		},
@@ -389,12 +422,12 @@ func TestValidateFFIFail(t *testing.T) {
 	cm := newTestContractManager()
 	mdi := cm.database.(*databasemocks.Plugin)
 
-	ffi := &core.FFI{
+	ffi := &fftypes.FFI{
 		Namespace: "default",
-		Methods: []*core.FFIMethod{
+		Methods: []*fftypes.FFIMethod{
 			{
 				Name: "sum",
-				Params: []*core.FFIParam{
+				Params: []*fftypes.FFIParam{
 					{
 						Name:   "x",
 						Schema: fftypes.JSONAnyPtr(`{"type": "integer", "details": {"type": "uint256"}}`),
@@ -404,7 +437,7 @@ func TestValidateFFIFail(t *testing.T) {
 						Schema: fftypes.JSONAnyPtr(`{"type": "integer", "details": {"type": "uint256"}}`),
 					},
 				},
-				Returns: []*core.FFIParam{
+				Returns: []*fftypes.FFIParam{
 					{
 						Name:   "z",
 						Schema: fftypes.JSONAnyPtr(`{"type": "integer", "details": {"type": "uint256"}}`),
@@ -414,15 +447,15 @@ func TestValidateFFIFail(t *testing.T) {
 			{
 				Name:        "sum",
 				Description: "Override of sum method with different args",
-				Params:      []*core.FFIParam{},
-				Returns:     []*core.FFIParam{},
+				Params:      []*fftypes.FFIParam{},
+				Returns:     []*fftypes.FFIParam{},
 			},
 		},
-		Events: []*core.FFIEvent{
+		Events: []*fftypes.FFIEvent{
 			{
-				FFIEventDefinition: core.FFIEventDefinition{
+				FFIEventDefinition: fftypes.FFIEventDefinition{
 					Name: "sum",
-					Params: []*core.FFIParam{
+					Params: []*fftypes.FFIParam{
 						{
 							Name:   "z",
 							Schema: fftypes.JSONAnyPtr(`{"type": "integer", "details": {"type": "uint256"}}`),
@@ -431,10 +464,10 @@ func TestValidateFFIFail(t *testing.T) {
 				},
 			},
 			{
-				FFIEventDefinition: core.FFIEventDefinition{
+				FFIEventDefinition: fftypes.FFIEventDefinition{
 					Name:        "sum",
 					Description: "Override of event with different params",
-					Params:      []*core.FFIParam{},
+					Params:      []*fftypes.FFIParam{},
 				},
 			},
 		},
@@ -450,14 +483,14 @@ func TestValidateFFIBadMethod(t *testing.T) {
 	cm := newTestContractManager()
 	mdi := cm.database.(*databasemocks.Plugin)
 
-	ffi := &core.FFI{
+	ffi := &fftypes.FFI{
 		Name:      "math",
 		Version:   "1.0.0",
 		Namespace: "default",
-		Methods: []*core.FFIMethod{
+		Methods: []*fftypes.FFIMethod{
 			{
 				Name: "",
-				Params: []*core.FFIParam{
+				Params: []*fftypes.FFIParam{
 					{
 						Name:   "x",
 						Schema: fftypes.JSONAnyPtr(`{"type": "integer", "details": {"type": "uint256"}}`),
@@ -467,7 +500,7 @@ func TestValidateFFIBadMethod(t *testing.T) {
 						Schema: fftypes.JSONAnyPtr(`{"type": "integer", "details": {"type": "uint256"}}`),
 					},
 				},
-				Returns: []*core.FFIParam{
+				Returns: []*fftypes.FFIParam{
 					{
 						Name:   "z",
 						Schema: fftypes.JSONAnyPtr(`{"type": "integer", "details": {"type": "uint256"}}`),
@@ -475,11 +508,11 @@ func TestValidateFFIBadMethod(t *testing.T) {
 				},
 			},
 		},
-		Events: []*core.FFIEvent{
+		Events: []*fftypes.FFIEvent{
 			{
-				FFIEventDefinition: core.FFIEventDefinition{
+				FFIEventDefinition: fftypes.FFIEventDefinition{
 					Name: "sum",
-					Params: []*core.FFIParam{
+					Params: []*fftypes.FFIParam{
 						{
 							Name:   "z",
 							Schema: fftypes.JSONAnyPtr(`{"type": "integer", "details": {"type": "uint256"}}`),
@@ -500,14 +533,14 @@ func TestValidateFFIBadEventParam(t *testing.T) {
 	cm := newTestContractManager()
 	mdi := cm.database.(*databasemocks.Plugin)
 
-	ffi := &core.FFI{
+	ffi := &fftypes.FFI{
 		Name:      "math",
 		Version:   "1.0.0",
 		Namespace: "default",
-		Methods: []*core.FFIMethod{
+		Methods: []*fftypes.FFIMethod{
 			{
 				Name: "sum",
-				Params: []*core.FFIParam{
+				Params: []*fftypes.FFIParam{
 					{
 						Name:   "x",
 						Schema: fftypes.JSONAnyPtr(`{"type": "integer", "details": {"type": "uint256"}}`),
@@ -517,7 +550,7 @@ func TestValidateFFIBadEventParam(t *testing.T) {
 						Schema: fftypes.JSONAnyPtr(`{"type": "integer", "details": {"type": "uint256"}}`),
 					},
 				},
-				Returns: []*core.FFIParam{
+				Returns: []*fftypes.FFIParam{
 					{
 						Name:   "z",
 						Schema: fftypes.JSONAnyPtr(`{"type": "integer", "details": {"type": "uint256"}}`),
@@ -525,11 +558,11 @@ func TestValidateFFIBadEventParam(t *testing.T) {
 				},
 			},
 		},
-		Events: []*core.FFIEvent{
+		Events: []*fftypes.FFIEvent{
 			{
-				FFIEventDefinition: core.FFIEventDefinition{
+				FFIEventDefinition: fftypes.FFIEventDefinition{
 					Name: "",
-					Params: []*core.FFIParam{
+					Params: []*fftypes.FFIParam{
 						{
 							Name:   "z",
 							Schema: fftypes.JSONAnyPtr(`{"type": "integer", "details": {"type": "uint256"}}`),
@@ -557,9 +590,9 @@ func TestAddContractListenerInline(t *testing.T) {
 				"address": "0x123",
 			}.String()),
 			Event: &core.FFISerializedEvent{
-				FFIEventDefinition: core.FFIEventDefinition{
+				FFIEventDefinition: fftypes.FFIEventDefinition{
 					Name: "changed",
-					Params: core.FFIParams{
+					Params: fftypes.FFIParams{
 						{
 							Name:   "value",
 							Schema: fftypes.JSONAnyPtr(`{"type": "integer"}`),
@@ -594,12 +627,12 @@ func TestAddContractListenerByEventPath(t *testing.T) {
 
 	interfaceID := fftypes.NewUUID()
 
-	event := &core.FFIEvent{
+	event := &fftypes.FFIEvent{
 		ID:        fftypes.NewUUID(),
 		Namespace: "ns1",
-		FFIEventDefinition: core.FFIEventDefinition{
+		FFIEventDefinition: fftypes.FFIEventDefinition{
 			Name: "changed",
-			Params: core.FFIParams{
+			Params: fftypes.FFIParams{
 				{
 					Name:   "value",
 					Schema: fftypes.JSONAnyPtr(`{"type": "integer"}`),
@@ -610,7 +643,7 @@ func TestAddContractListenerByEventPath(t *testing.T) {
 
 	sub := &core.ContractListenerInput{
 		ContractListener: core.ContractListener{
-			Interface: &core.FFIReference{
+			Interface: &fftypes.FFIReference{
 				ID: interfaceID,
 			},
 			Location: fftypes.JSONAnyPtr(fftypes.JSONObject{
@@ -625,7 +658,7 @@ func TestAddContractListenerByEventPath(t *testing.T) {
 	mbi.On("GenerateEventSignature", context.Background(), mock.Anything).Return("changed")
 	mdi.On("GetContractListeners", context.Background(), "ns1", mock.Anything).Return(nil, nil, nil)
 	mbi.On("AddContractListener", context.Background(), sub).Return(nil)
-	mdi.On("GetFFIByID", context.Background(), "ns1", interfaceID).Return(&core.FFI{}, nil)
+	mdi.On("GetFFIByID", context.Background(), "ns1", interfaceID).Return(&fftypes.FFI{}, nil)
 	mdi.On("GetFFIEvent", context.Background(), "ns1", interfaceID, sub.EventPath).Return(event, nil)
 	mdi.On("InsertContractListener", context.Background(), &sub.ContractListener).Return(nil)
 
@@ -644,7 +677,7 @@ func TestAddContractListenerBadLocation(t *testing.T) {
 
 	sub := &core.ContractListenerInput{
 		ContractListener: core.ContractListener{
-			Interface: &core.FFIReference{
+			Interface: &fftypes.FFIReference{
 				ID: fftypes.NewUUID(),
 			},
 			Location: fftypes.JSONAnyPtr(fftypes.JSONObject{
@@ -672,7 +705,7 @@ func TestAddContractListenerFFILookupFail(t *testing.T) {
 
 	sub := &core.ContractListenerInput{
 		ContractListener: core.ContractListener{
-			Interface: &core.FFIReference{
+			Interface: &fftypes.FFIReference{
 				ID: interfaceID,
 			},
 			Location: fftypes.JSONAnyPtr(fftypes.JSONObject{
@@ -702,7 +735,7 @@ func TestAddContractListenerEventLookupFail(t *testing.T) {
 
 	sub := &core.ContractListenerInput{
 		ContractListener: core.ContractListener{
-			Interface: &core.FFIReference{
+			Interface: &fftypes.FFIReference{
 				ID: interfaceID,
 			},
 			Location: fftypes.JSONAnyPtr(fftypes.JSONObject{
@@ -714,7 +747,7 @@ func TestAddContractListenerEventLookupFail(t *testing.T) {
 	}
 
 	mbi.On("NormalizeContractLocation", context.Background(), sub.Location).Return(sub.Location, nil)
-	mdi.On("GetFFIByID", context.Background(), "ns1", interfaceID).Return(&core.FFI{}, nil)
+	mdi.On("GetFFIByID", context.Background(), "ns1", interfaceID).Return(&fftypes.FFI{}, nil)
 	mdi.On("GetFFIEvent", context.Background(), "ns1", interfaceID, sub.EventPath).Return(nil, fmt.Errorf("pop"))
 
 	_, err := cm.AddContractListener(context.Background(), sub)
@@ -733,7 +766,7 @@ func TestAddContractListenerEventLookupNotFound(t *testing.T) {
 
 	sub := &core.ContractListenerInput{
 		ContractListener: core.ContractListener{
-			Interface: &core.FFIReference{
+			Interface: &fftypes.FFIReference{
 				ID: interfaceID,
 			},
 			Location: fftypes.JSONAnyPtr(fftypes.JSONObject{
@@ -745,7 +778,7 @@ func TestAddContractListenerEventLookupNotFound(t *testing.T) {
 	}
 
 	mbi.On("NormalizeContractLocation", context.Background(), sub.Location).Return(sub.Location, nil)
-	mdi.On("GetFFIByID", context.Background(), "ns1", interfaceID).Return(&core.FFI{}, nil)
+	mdi.On("GetFFIByID", context.Background(), "ns1", interfaceID).Return(&fftypes.FFI{}, nil)
 	mdi.On("GetFFIEvent", context.Background(), "ns1", interfaceID, sub.EventPath).Return(nil, nil)
 
 	_, err := cm.AddContractListener(context.Background(), sub)
@@ -913,12 +946,12 @@ func TestAddContractListenerValidateFail(t *testing.T) {
 				"address": "0x123",
 			}.String()),
 			Event: &core.FFISerializedEvent{
-				FFIEventDefinition: core.FFIEventDefinition{
+				FFIEventDefinition: fftypes.FFIEventDefinition{
 					Name: "changed",
-					Params: core.FFIParams{
+					Params: fftypes.FFIParams{
 						{
 							Name:   "value",
-							Schema: fftypes.JSONAnyPtr(`{"type": "number"}`),
+							Schema: fftypes.JSONAnyPtr(`{"type": "null"}`),
 						},
 					},
 				},
@@ -949,9 +982,9 @@ func TestAddContractListenerBlockchainFail(t *testing.T) {
 				"address": "0x123",
 			}.String()),
 			Event: &core.FFISerializedEvent{
-				FFIEventDefinition: core.FFIEventDefinition{
+				FFIEventDefinition: fftypes.FFIEventDefinition{
 					Name: "changed",
-					Params: core.FFIParams{
+					Params: fftypes.FFIParams{
 						{
 							Name:   "value",
 							Schema: fftypes.JSONAnyPtr(`{"type": "integer"}`),
@@ -986,9 +1019,9 @@ func TestAddContractListenerUpsertSubFail(t *testing.T) {
 				"address": "0x123",
 			}.String()),
 			Event: &core.FFISerializedEvent{
-				FFIEventDefinition: core.FFIEventDefinition{
+				FFIEventDefinition: fftypes.FFIEventDefinition{
 					Name: "changed",
-					Params: core.FFIParams{
+					Params: fftypes.FFIParams{
 						{
 							Name:   "value",
 							Schema: fftypes.JSONAnyPtr(`{"type": "integer"}`),
@@ -1020,7 +1053,7 @@ func TestAddContractAPIListener(t *testing.T) {
 
 	interfaceID := fftypes.NewUUID()
 	api := &core.ContractAPI{
-		Interface: &core.FFIReference{
+		Interface: &fftypes.FFIReference{
 			ID: interfaceID,
 		},
 		Location: fftypes.JSONAnyPtr(fftypes.JSONObject{
@@ -1030,15 +1063,15 @@ func TestAddContractAPIListener(t *testing.T) {
 	listener := &core.ContractListener{
 		Topic: "test-topic",
 	}
-	event := &core.FFIEvent{
-		FFIEventDefinition: core.FFIEventDefinition{
+	event := &fftypes.FFIEvent{
+		FFIEventDefinition: fftypes.FFIEventDefinition{
 			Name: "changed",
 		},
 	}
 
 	mdi.On("GetContractAPIByName", context.Background(), "ns1", "simple").Return(api, nil)
 	mbi.On("NormalizeContractLocation", context.Background(), api.Location).Return(listener.Location, nil)
-	mdi.On("GetFFIByID", context.Background(), "ns1", interfaceID).Return(&core.FFI{}, nil)
+	mdi.On("GetFFIByID", context.Background(), "ns1", interfaceID).Return(&fftypes.FFI{}, nil)
 	mdi.On("GetFFIEvent", context.Background(), "ns1", interfaceID, "changed").Return(event, nil)
 	mbi.On("GenerateEventSignature", context.Background(), mock.Anything).Return("changed")
 	mdi.On("GetContractListeners", context.Background(), "ns1", mock.Anything).Return(nil, nil, nil)
@@ -1097,7 +1130,7 @@ func TestAddContractAPIListenerFail(t *testing.T) {
 func TestGetFFI(t *testing.T) {
 	cm := newTestContractManager()
 	mdb := cm.database.(*databasemocks.Plugin)
-	mdb.On("GetFFI", mock.Anything, "ns1", "ffi", "v1.0.0").Return(&core.FFI{}, nil)
+	mdb.On("GetFFI", mock.Anything, "ns1", "ffi", "v1.0.0").Return(&fftypes.FFI{}, nil)
 	_, err := cm.GetFFI(context.Background(), "ffi", "v1.0.0")
 	assert.NoError(t, err)
 }
@@ -1108,14 +1141,14 @@ func TestGetFFIWithChildren(t *testing.T) {
 	mbi := cm.blockchain.(*blockchainmocks.Plugin)
 
 	cid := fftypes.NewUUID()
-	mdb.On("GetFFI", mock.Anything, "ns1", "ffi", "v1.0.0").Return(&core.FFI{ID: cid}, nil)
-	mdb.On("GetFFIMethods", mock.Anything, "ns1", mock.Anything).Return([]*core.FFIMethod{
+	mdb.On("GetFFI", mock.Anything, "ns1", "ffi", "v1.0.0").Return(&fftypes.FFI{ID: cid}, nil)
+	mdb.On("GetFFIMethods", mock.Anything, "ns1", mock.Anything).Return([]*fftypes.FFIMethod{
 		{ID: fftypes.NewUUID(), Name: "method1"},
 	}, nil, nil)
-	mdb.On("GetFFIEvents", mock.Anything, "ns1", mock.Anything).Return([]*core.FFIEvent{
-		{ID: fftypes.NewUUID(), FFIEventDefinition: core.FFIEventDefinition{Name: "event1"}},
+	mdb.On("GetFFIEvents", mock.Anything, "ns1", mock.Anything).Return([]*fftypes.FFIEvent{
+		{ID: fftypes.NewUUID(), FFIEventDefinition: fftypes.FFIEventDefinition{Name: "event1"}},
 	}, nil, nil)
-	mbi.On("GenerateEventSignature", mock.Anything, mock.MatchedBy(func(ev *core.FFIEventDefinition) bool {
+	mbi.On("GenerateEventSignature", mock.Anything, mock.MatchedBy(func(ev *fftypes.FFIEventDefinition) bool {
 		return ev.Name == "event1"
 	})).Return("event1Sig")
 
@@ -1130,7 +1163,7 @@ func TestGetFFIByID(t *testing.T) {
 	cm := newTestContractManager()
 	mdb := cm.database.(*databasemocks.Plugin)
 	cid := fftypes.NewUUID()
-	mdb.On("GetFFIByID", mock.Anything, "ns1", cid).Return(&core.FFI{}, nil)
+	mdb.On("GetFFIByID", mock.Anything, "ns1", cid).Return(&fftypes.FFI{}, nil)
 	_, err := cm.GetFFIByID(context.Background(), cid)
 	assert.NoError(t, err)
 }
@@ -1141,16 +1174,16 @@ func TestGetFFIByIDWithChildren(t *testing.T) {
 	mbi := cm.blockchain.(*blockchainmocks.Plugin)
 
 	cid := fftypes.NewUUID()
-	mdb.On("GetFFIByID", mock.Anything, "ns1", cid).Return(&core.FFI{
+	mdb.On("GetFFIByID", mock.Anything, "ns1", cid).Return(&fftypes.FFI{
 		ID: cid,
 	}, nil)
-	mdb.On("GetFFIMethods", mock.Anything, "ns1", mock.Anything).Return([]*core.FFIMethod{
+	mdb.On("GetFFIMethods", mock.Anything, "ns1", mock.Anything).Return([]*fftypes.FFIMethod{
 		{ID: fftypes.NewUUID(), Name: "method1"},
 	}, nil, nil)
-	mdb.On("GetFFIEvents", mock.Anything, "ns1", mock.Anything).Return([]*core.FFIEvent{
-		{ID: fftypes.NewUUID(), FFIEventDefinition: core.FFIEventDefinition{Name: "event1"}},
+	mdb.On("GetFFIEvents", mock.Anything, "ns1", mock.Anything).Return([]*fftypes.FFIEvent{
+		{ID: fftypes.NewUUID(), FFIEventDefinition: fftypes.FFIEventDefinition{Name: "event1"}},
 	}, nil, nil)
-	mbi.On("GenerateEventSignature", mock.Anything, mock.MatchedBy(func(ev *core.FFIEventDefinition) bool {
+	mbi.On("GenerateEventSignature", mock.Anything, mock.MatchedBy(func(ev *fftypes.FFIEventDefinition) bool {
 		return ev.Name == "event1"
 	})).Return("event1Sig")
 
@@ -1169,10 +1202,10 @@ func TestGetFFIByIDWithChildrenEventsFail(t *testing.T) {
 	mdb := cm.database.(*databasemocks.Plugin)
 
 	cid := fftypes.NewUUID()
-	mdb.On("GetFFIByID", mock.Anything, "ns1", cid).Return(&core.FFI{
+	mdb.On("GetFFIByID", mock.Anything, "ns1", cid).Return(&fftypes.FFI{
 		ID: cid,
 	}, nil)
-	mdb.On("GetFFIMethods", mock.Anything, "ns1", mock.Anything).Return([]*core.FFIMethod{
+	mdb.On("GetFFIMethods", mock.Anything, "ns1", mock.Anything).Return([]*fftypes.FFIMethod{
 		{ID: fftypes.NewUUID(), Name: "method1"},
 	}, nil, nil)
 	mdb.On("GetFFIEvents", mock.Anything, "ns1", mock.Anything).Return(nil, nil, fmt.Errorf("pop"))
@@ -1188,7 +1221,7 @@ func TestGetFFIByIDWithChildrenMethodsFail(t *testing.T) {
 	mdb := cm.database.(*databasemocks.Plugin)
 
 	cid := fftypes.NewUUID()
-	mdb.On("GetFFIByID", mock.Anything, "ns1", cid).Return(&core.FFI{
+	mdb.On("GetFFIByID", mock.Anything, "ns1", cid).Return(&fftypes.FFI{
 		ID: cid,
 	}, nil)
 	mdb.On("GetFFIMethods", mock.Anything, "ns1", mock.Anything).Return(nil, nil, fmt.Errorf("pop"))
@@ -1230,7 +1263,7 @@ func TestGetFFIs(t *testing.T) {
 	cm := newTestContractManager()
 	mdb := cm.database.(*databasemocks.Plugin)
 	filter := database.FFIQueryFactory.NewFilter(context.Background()).And()
-	mdb.On("GetFFIs", mock.Anything, "ns1", filter).Return([]*core.FFI{}, &database.FilterResult{}, nil)
+	mdb.On("GetFFIs", mock.Anything, "ns1", filter).Return([]*fftypes.FFI{}, &database.FilterResult{}, nil)
 	_, _, err := cm.GetFFIs(context.Background(), filter)
 	assert.NoError(t, err)
 }
@@ -1246,11 +1279,11 @@ func TestInvokeContract(t *testing.T) {
 		Type:      core.CallTypeInvoke,
 		Interface: fftypes.NewUUID(),
 		Location:  fftypes.JSONAnyPtr(""),
-		Method: &core.FFIMethod{
+		Method: &fftypes.FFIMethod{
 			Name:    "doStuff",
 			ID:      fftypes.NewUUID(),
-			Params:  core.FFIParams{},
-			Returns: core.FFIParams{},
+			Params:  fftypes.FFIParams{},
+			Returns: fftypes.FFIParams{},
 		},
 	}
 
@@ -1286,11 +1319,11 @@ func TestInvokeContractConfirm(t *testing.T) {
 		Type:      core.CallTypeInvoke,
 		Interface: fftypes.NewUUID(),
 		Location:  fftypes.JSONAnyPtr(""),
-		Method: &core.FFIMethod{
+		Method: &fftypes.FFIMethod{
 			Name:    "doStuff",
 			ID:      fftypes.NewUUID(),
-			Params:  core.FFIParams{},
-			Returns: core.FFIParams{},
+			Params:  fftypes.FFIParams{},
+			Returns: fftypes.FFIParams{},
 		},
 	}
 
@@ -1332,11 +1365,11 @@ func TestInvokeContractFail(t *testing.T) {
 		Type:      core.CallTypeInvoke,
 		Interface: fftypes.NewUUID(),
 		Location:  fftypes.JSONAnyPtr(""),
-		Method: &core.FFIMethod{
+		Method: &fftypes.FFIMethod{
 			Name:    "doStuff",
 			ID:      fftypes.NewUUID(),
-			Params:  core.FFIParams{},
-			Returns: core.FFIParams{},
+			Params:  fftypes.FFIParams{},
+			Returns: fftypes.FFIParams{},
 		},
 	}
 
@@ -1405,11 +1438,11 @@ func TestInvokeContractTXFail(t *testing.T) {
 		Type:      core.CallTypeInvoke,
 		Interface: fftypes.NewUUID(),
 		Location:  fftypes.JSONAnyPtr(""),
-		Method: &core.FFIMethod{
+		Method: &fftypes.FFIMethod{
 			Name:    "doStuff",
 			ID:      fftypes.NewUUID(),
-			Params:  core.FFIParams{},
-			Returns: core.FFIParams{},
+			Params:  fftypes.FFIParams{},
+			Returns: fftypes.FFIParams{},
 		},
 	}
 
@@ -1449,9 +1482,9 @@ func TestInvokeContractMethodBadInput(t *testing.T) {
 		Type:      core.CallTypeInvoke,
 		Interface: fftypes.NewUUID(),
 		Location:  fftypes.JSONAnyPtr(""),
-		Method: &core.FFIMethod{
+		Method: &fftypes.FFIMethod{
 			Name: "sum",
-			Params: core.FFIParams{
+			Params: fftypes.FFIParams{
 				{
 					Name:   "x",
 					Schema: fftypes.JSONAnyPtr(`{"type": "integer", "details": {"type": "uint256"}}`),
@@ -1461,7 +1494,7 @@ func TestInvokeContractMethodBadInput(t *testing.T) {
 					Schema: fftypes.JSONAnyPtr(`{"type": "integer", "details": {"type": "uint256"}}`),
 				},
 			},
-			Returns: core.FFIParams{
+			Returns: fftypes.FFIParams{
 
 				{
 					Name:   "sum",
@@ -1487,11 +1520,11 @@ func TestQueryContract(t *testing.T) {
 		Type:      core.CallTypeQuery,
 		Interface: fftypes.NewUUID(),
 		Location:  fftypes.JSONAnyPtr(""),
-		Method: &core.FFIMethod{
+		Method: &fftypes.FFIMethod{
 			Name:    "doStuff",
 			ID:      fftypes.NewUUID(),
-			Params:  core.FFIParams{},
-			Returns: core.FFIParams{},
+			Params:  fftypes.FFIParams{},
+			Returns: fftypes.FFIParams{},
 		},
 	}
 
@@ -1516,11 +1549,11 @@ func TestCallContractInvalidType(t *testing.T) {
 	req := &core.ContractCallRequest{
 		Interface: fftypes.NewUUID(),
 		Location:  fftypes.JSONAnyPtr(""),
-		Method: &core.FFIMethod{
+		Method: &fftypes.FFIMethod{
 			Name:    "doStuff",
 			ID:      fftypes.NewUUID(),
-			Params:  core.FFIParams{},
-			Returns: core.FFIParams{},
+			Params:  fftypes.FFIParams{},
+			Returns: fftypes.FFIParams{},
 		},
 	}
 
@@ -1612,21 +1645,21 @@ func TestGetContractAPIListeners(t *testing.T) {
 
 	interfaceID := fftypes.NewUUID()
 	api := &core.ContractAPI{
-		Interface: &core.FFIReference{
+		Interface: &fftypes.FFIReference{
 			ID: interfaceID,
 		},
 		Location: fftypes.JSONAnyPtr(fftypes.JSONObject{
 			"address": "0x123",
 		}.String()),
 	}
-	event := &core.FFIEvent{
-		FFIEventDefinition: core.FFIEventDefinition{
+	event := &fftypes.FFIEvent{
+		FFIEventDefinition: fftypes.FFIEventDefinition{
 			Name: "changed",
 		},
 	}
 
 	mdi.On("GetContractAPIByName", context.Background(), "ns1", "simple").Return(api, nil)
-	mdi.On("GetFFIByID", context.Background(), "ns1", interfaceID).Return(&core.FFI{}, nil)
+	mdi.On("GetFFIByID", context.Background(), "ns1", interfaceID).Return(&fftypes.FFI{}, nil)
 	mdi.On("GetFFIEvent", context.Background(), "ns1", interfaceID, "changed").Return(event, nil)
 	mbi.On("GenerateEventSignature", context.Background(), mock.Anything).Return("changed")
 	mdi.On("GetContractListeners", context.Background(), "ns1", mock.Anything).Return(nil, nil, nil)
@@ -1671,7 +1704,7 @@ func TestGetContractAPIListenersEventNotFound(t *testing.T) {
 
 	interfaceID := fftypes.NewUUID()
 	api := &core.ContractAPI{
-		Interface: &core.FFIReference{
+		Interface: &fftypes.FFIReference{
 			ID: interfaceID,
 		},
 		Location: fftypes.JSONAnyPtr(fftypes.JSONObject{
@@ -1680,7 +1713,7 @@ func TestGetContractAPIListenersEventNotFound(t *testing.T) {
 	}
 
 	mdi.On("GetContractAPIByName", context.Background(), "ns1", "simple").Return(api, nil)
-	mdi.On("GetFFIByID", context.Background(), "ns1", interfaceID).Return(&core.FFI{}, nil)
+	mdi.On("GetFFIByID", context.Background(), "ns1", interfaceID).Return(&fftypes.FFI{}, nil)
 	mdi.On("GetFFIEvent", context.Background(), "ns1", interfaceID, "changed").Return(nil, nil)
 
 	f := database.ContractListenerQueryFactory.NewFilter(context.Background())
@@ -1746,14 +1779,14 @@ func TestInvokeContractAPI(t *testing.T) {
 		Type:      core.CallTypeInvoke,
 		Interface: fftypes.NewUUID(),
 		Location:  fftypes.JSONAnyPtr(""),
-		Method: &core.FFIMethod{
+		Method: &fftypes.FFIMethod{
 			ID:   fftypes.NewUUID(),
 			Name: "peel",
 		},
 	}
 
 	api := &core.ContractAPI{
-		Interface: &core.FFIReference{
+		Interface: &fftypes.FFIReference{
 			ID: fftypes.NewUUID(),
 		},
 		Location: fftypes.JSONAnyPtr(""),
@@ -1789,7 +1822,7 @@ func TestInvokeContractAPIFailContractLookup(t *testing.T) {
 		Type:      core.CallTypeInvoke,
 		Interface: fftypes.NewUUID(),
 		Location:  fftypes.JSONAnyPtr(""),
-		Method: &core.FFIMethod{
+		Method: &fftypes.FFIMethod{
 			ID: fftypes.NewUUID(),
 		},
 	}
@@ -1810,7 +1843,7 @@ func TestInvokeContractAPIContractNotFound(t *testing.T) {
 		Type:      core.CallTypeInvoke,
 		Interface: fftypes.NewUUID(),
 		Location:  fftypes.JSONAnyPtr(""),
-		Method: &core.FFIMethod{
+		Method: &fftypes.FFIMethod{
 			ID: fftypes.NewUUID(),
 		},
 	}
@@ -1870,18 +1903,18 @@ func TestGetContractAPIInterface(t *testing.T) {
 	api := &core.ContractAPI{
 		Namespace: "ns1",
 		Name:      "banana",
-		Interface: &core.FFIReference{ID: interfaceID},
+		Interface: &fftypes.FFIReference{ID: interfaceID},
 	}
 
 	mdb.On("GetContractAPIByName", mock.Anything, "ns1", "banana").Return(api, nil)
-	mdb.On("GetFFIByID", mock.Anything, "ns1", interfaceID).Return(&core.FFI{}, nil)
-	mdb.On("GetFFIMethods", mock.Anything, "ns1", mock.Anything).Return([]*core.FFIMethod{
+	mdb.On("GetFFIByID", mock.Anything, "ns1", interfaceID).Return(&fftypes.FFI{}, nil)
+	mdb.On("GetFFIMethods", mock.Anything, "ns1", mock.Anything).Return([]*fftypes.FFIMethod{
 		{ID: fftypes.NewUUID(), Name: "method1"},
 	}, nil, nil)
-	mdb.On("GetFFIEvents", mock.Anything, "ns1", mock.Anything).Return([]*core.FFIEvent{
-		{ID: fftypes.NewUUID(), FFIEventDefinition: core.FFIEventDefinition{Name: "event1"}},
+	mdb.On("GetFFIEvents", mock.Anything, "ns1", mock.Anything).Return([]*fftypes.FFIEvent{
+		{ID: fftypes.NewUUID(), FFIEventDefinition: fftypes.FFIEventDefinition{Name: "event1"}},
 	}, nil, nil)
-	mbi.On("GenerateEventSignature", mock.Anything, mock.MatchedBy(func(ev *core.FFIEventDefinition) bool {
+	mbi.On("GenerateEventSignature", mock.Anything, mock.MatchedBy(func(ev *fftypes.FFIEventDefinition) bool {
 		return ev.Name == "event1"
 	})).Return("event1Sig")
 
@@ -1917,14 +1950,14 @@ func TestResolveContractAPI(t *testing.T) {
 		Namespace: "ns1",
 		Location:  fftypes.JSONAnyPtr(""),
 		Name:      "banana",
-		Interface: &core.FFIReference{
+		Interface: &fftypes.FFIReference{
 			ID: fftypes.NewUUID(),
 		},
 	}
 
 	mbi.On("NormalizeContractLocation", context.Background(), api.Location).Return(api.Location, nil)
 	mdb.On("GetContractAPIByName", mock.Anything, api.Namespace, api.Name).Return(nil, nil)
-	mdb.On("GetFFIByID", mock.Anything, "ns1", api.Interface.ID).Return(&core.FFI{}, nil)
+	mdb.On("GetFFIByID", mock.Anything, "ns1", api.Interface.ID).Return(&fftypes.FFI{}, nil)
 
 	err := cm.ResolveContractAPI(context.Background(), "http://localhost/api", api)
 	assert.NoError(t, err)
@@ -1942,7 +1975,7 @@ func TestResolveContractAPIBadLocation(t *testing.T) {
 		Namespace: "ns1",
 		Location:  fftypes.JSONAnyPtr(""),
 		Name:      "banana",
-		Interface: &core.FFIReference{
+		Interface: &fftypes.FFIReference{
 			ID: fftypes.NewUUID(),
 		},
 	}
@@ -1966,7 +1999,7 @@ func TestResolveContractAPICannotChangeLocation(t *testing.T) {
 		Namespace: "ns1",
 		Location:  fftypes.JSONAnyPtr(`"old"`),
 		Name:      "banana",
-		Interface: &core.FFIReference{
+		Interface: &fftypes.FFIReference{
 			ID: fftypes.NewUUID(),
 		},
 	}
@@ -1975,7 +2008,7 @@ func TestResolveContractAPICannotChangeLocation(t *testing.T) {
 		Namespace: "ns1",
 		Location:  fftypes.JSONAnyPtr(`"new"`),
 		Name:      "banana",
-		Interface: &core.FFIReference{
+		Interface: &fftypes.FFIReference{
 			ID: fftypes.NewUUID(),
 		},
 	}
@@ -2000,7 +2033,7 @@ func TestResolveContractAPIInterfaceName(t *testing.T) {
 		Namespace: "ns1",
 		Location:  fftypes.JSONAnyPtr(""),
 		Name:      "banana",
-		Interface: &core.FFIReference{
+		Interface: &fftypes.FFIReference{
 			Name:    "my-ffi",
 			Version: "1",
 		},
@@ -2009,7 +2042,7 @@ func TestResolveContractAPIInterfaceName(t *testing.T) {
 
 	mbi.On("NormalizeContractLocation", context.Background(), api.Location).Return(api.Location, nil)
 	mdb.On("GetContractAPIByName", mock.Anything, api.Namespace, api.Name).Return(nil, nil)
-	mdb.On("GetFFI", mock.Anything, "ns1", "my-ffi", "1").Return(&core.FFI{ID: interfaceID}, nil)
+	mdb.On("GetFFI", mock.Anything, "ns1", "my-ffi", "1").Return(&fftypes.FFI{ID: interfaceID}, nil)
 
 	err := cm.ResolveContractAPI(context.Background(), "http://localhost/api", api)
 	assert.NoError(t, err)
@@ -2050,7 +2083,7 @@ func TestResolveContractAPIInterfaceIDFail(t *testing.T) {
 		Namespace: "ns1",
 		Location:  fftypes.JSONAnyPtr(""),
 		Name:      "banana",
-		Interface: &core.FFIReference{
+		Interface: &fftypes.FFIReference{
 			ID: fftypes.NewUUID(),
 		},
 	}
@@ -2076,7 +2109,7 @@ func TestResolveContractAPIInterfaceIDNotFound(t *testing.T) {
 		Namespace: "ns1",
 		Location:  fftypes.JSONAnyPtr(""),
 		Name:      "banana",
-		Interface: &core.FFIReference{
+		Interface: &fftypes.FFIReference{
 			ID: fftypes.NewUUID(),
 		},
 	}
@@ -2102,7 +2135,7 @@ func TestResolveContractAPIInterfaceNameFail(t *testing.T) {
 		Namespace: "ns1",
 		Location:  fftypes.JSONAnyPtr(""),
 		Name:      "banana",
-		Interface: &core.FFIReference{
+		Interface: &fftypes.FFIReference{
 			Name:    "my-ffi",
 			Version: "1",
 		},
@@ -2129,7 +2162,7 @@ func TestResolveContractAPIInterfaceNameNotFound(t *testing.T) {
 		Namespace: "ns1",
 		Location:  fftypes.JSONAnyPtr(""),
 		Name:      "banana",
-		Interface: &core.FFIReference{
+		Interface: &fftypes.FFIReference{
 			Name:    "my-ffi",
 			Version: "1",
 		},
@@ -2156,7 +2189,7 @@ func TestResolveContractAPIInterfaceNoVersion(t *testing.T) {
 		Namespace: "ns1",
 		Location:  fftypes.JSONAnyPtr(""),
 		Name:      "banana",
-		Interface: &core.FFIReference{
+		Interface: &fftypes.FFIReference{
 			Name: "my-ffi",
 		},
 	}
@@ -2173,7 +2206,7 @@ func TestResolveContractAPIInterfaceNoVersion(t *testing.T) {
 
 func TestValidateFFIParamBadSchemaJSON(t *testing.T) {
 	cm := newTestContractManager()
-	param := &core.FFIParam{
+	param := &fftypes.FFIParam{
 		Name:   "x",
 		Schema: fftypes.JSONAnyPtr(`{"type": "integer"`),
 	}
@@ -2183,7 +2216,7 @@ func TestValidateFFIParamBadSchemaJSON(t *testing.T) {
 
 func TestCheckParamSchemaBadSchema(t *testing.T) {
 	cm := newTestContractManager()
-	param := &core.FFIParam{
+	param := &fftypes.FFIParam{
 		Name:   "x",
 		Schema: fftypes.JSONAnyPtr(`{"type": "integer"`),
 	}
@@ -2193,7 +2226,7 @@ func TestCheckParamSchemaBadSchema(t *testing.T) {
 
 func TestCheckParamSchemaCompileFail(t *testing.T) {
 	cm := newTestContractManager()
-	param := &core.FFIParam{
+	param := &fftypes.FFIParam{
 		Name:   "x",
 		Schema: fftypes.JSONAnyPtr(``),
 	}
@@ -2215,10 +2248,10 @@ func TestAddJSONSchemaExtension(t *testing.T) {
 func TestGenerateFFI(t *testing.T) {
 	cm := newTestContractManager()
 	mbi := cm.blockchain.(*blockchainmocks.Plugin)
-	mbi.On("GenerateFFI", mock.Anything, mock.Anything).Return(&core.FFI{
+	mbi.On("GenerateFFI", mock.Anything, mock.Anything).Return(&fftypes.FFI{
 		Name: "generated",
 	}, nil)
-	ffi, err := cm.GenerateFFI(context.Background(), &core.FFIGenerationRequest{})
+	ffi, err := cm.GenerateFFI(context.Background(), &fftypes.FFIGenerationRequest{})
 	assert.NoError(t, err)
 	assert.NotNil(t, ffi)
 	assert.Equal(t, "generated", ffi.Name)
diff --git a/internal/contracts/operations_test.go b/internal/contracts/operations_test.go
index 25c9cd6fe..ee244ad45 100644
--- a/internal/contracts/operations_test.go
+++ b/internal/contracts/operations_test.go
@@ -40,7 +40,7 @@ func TestPrepareAndRunBlockchainInvoke(t *testing.T) {
 	req := &core.ContractCallRequest{
 		Key:      "0x123",
 		Location: fftypes.JSONAnyPtr(`{"address":"0x1111"}`),
-		Method: &core.FFIMethod{
+		Method: &fftypes.FFIMethod{
 			Name: "set",
 		},
 		Input: map[string]interface{}{
@@ -53,7 +53,7 @@ func TestPrepareAndRunBlockchainInvoke(t *testing.T) {
 	mbi := cm.blockchain.(*blockchainmocks.Plugin)
 	mbi.On("InvokeContract", context.Background(), "ns1:"+op.ID.String(), "0x123", mock.MatchedBy(func(loc *fftypes.JSONAny) bool {
 		return loc.String() == req.Location.String()
-	}), mock.MatchedBy(func(method *core.FFIMethod) bool {
+	}), mock.MatchedBy(func(method *fftypes.FFIMethod) bool {
 		return method.Name == req.Method.Name
 	}), req.Input, req.Options).Return(nil)
 
diff --git a/internal/database/sqlcommon/contractapis_sql_test.go b/internal/database/sqlcommon/contractapis_sql_test.go
index c7552f268..20d2920b2 100644
--- a/internal/database/sqlcommon/contractapis_sql_test.go
+++ b/internal/database/sqlcommon/contractapis_sql_test.go
@@ -46,7 +46,7 @@ func TestContractAPIE2EWithDB(t *testing.T) {
 		ID:        apiID,
 		Namespace: "ns1",
 		Name:      "banana",
-		Interface: &core.FFIReference{
+		Interface: &fftypes.FFIReference{
 			ID:      interfaceID,
 			Name:    "banana",
 			Version: "v1.0.0",
@@ -102,7 +102,7 @@ func TestContractAPIDBFailInsert(t *testing.T) {
 	mock.ExpectQuery("SELECT .*").WillReturnRows(rows)
 	// mock.ExpectQuery("INSERT .*").WillReturnError(fmt.Errorf("pop"))
 	api := &core.ContractAPI{
-		Interface: &core.FFIReference{},
+		Interface: &fftypes.FFIReference{},
 	}
 	err := s.UpsertContractAPI(context.Background(), api)
 	assert.Regexp(t, "FF10116", err)
@@ -117,7 +117,7 @@ func TestContractAPIDBFailUpdate(t *testing.T) {
 	mock.ExpectQuery("SELECT .*").WillReturnRows(rows)
 	mock.ExpectQuery("UPDATE .*").WillReturnError(fmt.Errorf("pop"))
 	api := &core.ContractAPI{
-		Interface: &core.FFIReference{},
+		Interface: &fftypes.FFIReference{},
 	}
 	err := s.UpsertContractAPI(context.Background(), api)
 	assert.Regexp(t, "pop", err)
diff --git a/internal/database/sqlcommon/contractlisteners_sql_test.go b/internal/database/sqlcommon/contractlisteners_sql_test.go
index 06dfddb95..da06e157b 100644
--- a/internal/database/sqlcommon/contractlisteners_sql_test.go
+++ b/internal/database/sqlcommon/contractlisteners_sql_test.go
@@ -39,11 +39,11 @@ func TestContractListenerE2EWithDB(t *testing.T) {
 	locationJson, _ := json.Marshal(location)
 	sub := &core.ContractListener{
 		ID: fftypes.NewUUID(),
-		Interface: &core.FFIReference{
+		Interface: &fftypes.FFIReference{
 			ID: fftypes.NewUUID(),
 		},
 		Event: &core.FFISerializedEvent{
-			FFIEventDefinition: core.FFIEventDefinition{
+			FFIEventDefinition: fftypes.FFIEventDefinition{
 				Name: "event1",
 			},
 		},
diff --git a/internal/database/sqlcommon/ffi_events_sql_test.go b/internal/database/sqlcommon/ffi_events_sql_test.go
index 2458bbd07..80ba6e8d9 100644
--- a/internal/database/sqlcommon/ffi_events_sql_test.go
+++ b/internal/database/sqlcommon/ffi_events_sql_test.go
@@ -38,15 +38,15 @@ func TestFFIEventsE2EWithDB(t *testing.T) {
 	// Create a new event entry
 	interfaceID := fftypes.NewUUID()
 	eventID := fftypes.NewUUID()
-	event := &core.FFIEvent{
+	event := &fftypes.FFIEvent{
 		ID:        eventID,
 		Interface: interfaceID,
 		Namespace: "ns",
 		Pathname:  "Changed_1",
-		FFIEventDefinition: core.FFIEventDefinition{
+		FFIEventDefinition: fftypes.FFIEventDefinition{
 			Name:        "Changed",
 			Description: "Things changed",
-			Params: core.FFIParams{
+			Params: fftypes.FFIParams{
 				{
 					Name:   "value",
 					Schema: fftypes.JSONAnyPtr(`{"type": "integer"}`),
@@ -83,7 +83,7 @@ func TestFFIEventsE2EWithDB(t *testing.T) {
 	assert.Equal(t, string(eventJson), string(eventReadJson))
 
 	// Update event
-	event.Params = core.FFIParams{}
+	event.Params = fftypes.FFIParams{}
 	err = s.UpsertFFIEvent(ctx, event)
 	assert.NoError(t, err)
 
@@ -93,7 +93,7 @@ func TestFFIEventsE2EWithDB(t *testing.T) {
 func TestFFIEventDBFailBeginTransaction(t *testing.T) {
 	s, mock := newMockProvider().init()
 	mock.ExpectBegin().WillReturnError(fmt.Errorf("pop"))
-	err := s.UpsertFFIEvent(context.Background(), &core.FFIEvent{})
+	err := s.UpsertFFIEvent(context.Background(), &fftypes.FFIEvent{})
 	assert.Regexp(t, "FF10114", err)
 	assert.NoError(t, mock.ExpectationsWereMet())
 }
@@ -102,7 +102,7 @@ func TestFFIEventDBFailSelect(t *testing.T) {
 	s, mock := newMockProvider().init()
 	mock.ExpectBegin()
 	mock.ExpectQuery("SELECT .*").WillReturnError(fmt.Errorf("pop"))
-	err := s.UpsertFFIEvent(context.Background(), &core.FFIEvent{})
+	err := s.UpsertFFIEvent(context.Background(), &fftypes.FFIEvent{})
 	assert.Regexp(t, "pop", err)
 	assert.NoError(t, mock.ExpectationsWereMet())
 }
@@ -112,7 +112,7 @@ func TestFFIEventDBFailInsert(t *testing.T) {
 	s, mock := newMockProvider().init()
 	mock.ExpectBegin()
 	mock.ExpectQuery("SELECT .*").WillReturnRows(rows)
-	event := &core.FFIEvent{
+	event := &fftypes.FFIEvent{
 		ID: fftypes.NewUUID(),
 	}
 	err := s.UpsertFFIEvent(context.Background(), event)
@@ -127,7 +127,7 @@ func TestFFIEventDBFailUpdate(t *testing.T) {
 	mock.ExpectBegin()
 	mock.ExpectQuery("SELECT .*").WillReturnRows(rows)
 	mock.ExpectQuery("UPDATE .*").WillReturnError(fmt.Errorf("pop"))
-	event := &core.FFIEvent{
+	event := &fftypes.FFIEvent{
 		ID: fftypes.NewUUID(),
 	}
 	err := s.UpsertFFIEvent(context.Background(), event)
diff --git a/internal/database/sqlcommon/ffi_methods_sql_test.go b/internal/database/sqlcommon/ffi_methods_sql_test.go
index e8a48b919..e0edf453b 100644
--- a/internal/database/sqlcommon/ffi_methods_sql_test.go
+++ b/internal/database/sqlcommon/ffi_methods_sql_test.go
@@ -38,20 +38,20 @@ func TestFFIMethodsE2EWithDB(t *testing.T) {
 	// Create a new method entry
 	interfaceID := fftypes.NewUUID()
 	methodID := fftypes.NewUUID()
-	method := &core.FFIMethod{
+	method := &fftypes.FFIMethod{
 		ID:          methodID,
 		Interface:   interfaceID,
 		Name:        "Set",
 		Namespace:   "ns",
 		Pathname:    "Set_1",
 		Description: "Sets things",
-		Params: core.FFIParams{
+		Params: fftypes.FFIParams{
 			{
 				Name:   "value",
 				Schema: fftypes.JSONAnyPtr(`{"type": "integer"}`),
 			},
 		},
-		Returns: core.FFIParams{
+		Returns: fftypes.FFIParams{
 			{
 				Name:   "value",
 				Schema: fftypes.JSONAnyPtr(`{"type": "integer"}`),
@@ -87,7 +87,7 @@ func TestFFIMethodsE2EWithDB(t *testing.T) {
 	assert.Equal(t, string(methodJson), string(methodReadJson))
 
 	// Update method
-	method.Params = core.FFIParams{}
+	method.Params = fftypes.FFIParams{}
 	err = s.UpsertFFIMethod(ctx, method)
 	assert.NoError(t, err)
 
@@ -105,7 +105,7 @@ func TestFFIMethodsE2EWithDB(t *testing.T) {
 func TestFFIMethodDBFailBeginTransaction(t *testing.T) {
 	s, mock := newMockProvider().init()
 	mock.ExpectBegin().WillReturnError(fmt.Errorf("pop"))
-	err := s.UpsertFFIMethod(context.Background(), &core.FFIMethod{})
+	err := s.UpsertFFIMethod(context.Background(), &fftypes.FFIMethod{})
 	assert.Regexp(t, "FF10114", err)
 	assert.NoError(t, mock.ExpectationsWereMet())
 }
@@ -114,7 +114,7 @@ func TestFFIMethodDBFailSelect(t *testing.T) {
 	s, mock := newMockProvider().init()
 	mock.ExpectBegin()
 	mock.ExpectQuery("SELECT .*").WillReturnError(fmt.Errorf("pop"))
-	err := s.UpsertFFIMethod(context.Background(), &core.FFIMethod{})
+	err := s.UpsertFFIMethod(context.Background(), &fftypes.FFIMethod{})
 	assert.Regexp(t, "pop", err)
 	assert.NoError(t, mock.ExpectationsWereMet())
 }
@@ -124,7 +124,7 @@ func TestFFIMethodDBFailInsert(t *testing.T) {
 	s, mock := newMockProvider().init()
 	mock.ExpectBegin()
 	mock.ExpectQuery("SELECT .*").WillReturnRows(rows)
-	event := &core.FFIMethod{
+	event := &fftypes.FFIMethod{
 		ID: fftypes.NewUUID(),
 	}
 	err := s.UpsertFFIMethod(context.Background(), event)
@@ -139,7 +139,7 @@ func TestFFIMethodDBFailUpdate(t *testing.T) {
 	mock.ExpectBegin()
 	mock.ExpectQuery("SELECT .*").WillReturnRows(rows)
 	mock.ExpectQuery("UPDATE .*").WillReturnError(fmt.Errorf("pop"))
-	event := &core.FFIMethod{
+	event := &fftypes.FFIMethod{
 		ID: fftypes.NewUUID(),
 	}
 	err := s.UpsertFFIMethod(context.Background(), event)
diff --git a/internal/database/sqlcommon/ffi_sql_test.go b/internal/database/sqlcommon/ffi_sql_test.go
index f6c4d06be..a7338dbab 100644
--- a/internal/database/sqlcommon/ffi_sql_test.go
+++ b/internal/database/sqlcommon/ffi_sql_test.go
@@ -39,17 +39,17 @@ func TestFFIE2EWithDB(t *testing.T) {
 	// Create a new FFI
 	id := fftypes.NewUUID()
 
-	ffi := &core.FFI{
+	ffi := &fftypes.FFI{
 		ID:          id,
 		Namespace:   "ns1",
 		Name:        "math",
 		Version:     "v1.0.0",
 		Description: "Does things and stuff",
 		Message:     fftypes.NewUUID(),
-		Methods: []*core.FFIMethod{
+		Methods: []*fftypes.FFIMethod{
 			{
 				Name: "sum",
-				Params: core.FFIParams{
+				Params: fftypes.FFIParams{
 					{
 						Name:   "x",
 						Schema: fftypes.JSONAnyPtr(`{"type": "integer""}`),
@@ -59,7 +59,7 @@ func TestFFIE2EWithDB(t *testing.T) {
 						Schema: fftypes.JSONAnyPtr(`{"type": "integer""}`),
 					},
 				},
-				Returns: core.FFIParams{
+				Returns: fftypes.FFIParams{
 					{
 						Name:   "result",
 						Schema: fftypes.JSONAnyPtr(`{"type": "integer""}`),
@@ -104,7 +104,7 @@ func TestFFIE2EWithDB(t *testing.T) {
 func TestFFIDBFailBeginTransaction(t *testing.T) {
 	s, mock := newMockProvider().init()
 	mock.ExpectBegin().WillReturnError(fmt.Errorf("pop"))
-	err := s.UpsertFFI(context.Background(), &core.FFI{})
+	err := s.UpsertFFI(context.Background(), &fftypes.FFI{})
 	assert.Regexp(t, "FF10114", err)
 	assert.NoError(t, mock.ExpectationsWereMet())
 }
@@ -113,7 +113,7 @@ func TestFFIDBFailSelect(t *testing.T) {
 	s, mock := newMockProvider().init()
 	mock.ExpectBegin()
 	mock.ExpectQuery("SELECT .*").WillReturnError(fmt.Errorf("pop"))
-	err := s.UpsertFFI(context.Background(), &core.FFI{})
+	err := s.UpsertFFI(context.Background(), &fftypes.FFI{})
 	assert.Regexp(t, "pop", err)
 	assert.NoError(t, mock.ExpectationsWereMet())
 }
@@ -123,7 +123,7 @@ func TestFFIDBFailInsert(t *testing.T) {
 	s, mock := newMockProvider().init()
 	mock.ExpectBegin()
 	mock.ExpectQuery("SELECT .*").WillReturnRows(rows)
-	ffi := &core.FFI{
+	ffi := &fftypes.FFI{
 		ID: fftypes.NewUUID(),
 	}
 	err := s.UpsertFFI(context.Background(), ffi)
@@ -138,7 +138,7 @@ func TestFFIDBFailUpdate(t *testing.T) {
 	mock.ExpectBegin()
 	mock.ExpectQuery("SELECT .*").WillReturnRows(rows)
 	mock.ExpectQuery("UPDATE .*").WillReturnError(fmt.Errorf("pop"))
-	ffi := &core.FFI{
+	ffi := &fftypes.FFI{
 		ID: fftypes.NewUUID(),
 	}
 	err := s.UpsertFFI(context.Background(), ffi)
diff --git a/internal/definitions/handler_contracts_test.go b/internal/definitions/handler_contracts_test.go
index e72d655dd..14d013740 100644
--- a/internal/definitions/handler_contracts_test.go
+++ b/internal/definitions/handler_contracts_test.go
@@ -31,16 +31,16 @@ import (
 	"github.com/stretchr/testify/mock"
 )
 
-func testFFI() *core.FFI {
-	return &core.FFI{
+func testFFI() *fftypes.FFI {
+	return &fftypes.FFI{
 		ID:        fftypes.NewUUID(),
 		Namespace: "ns1",
 		Name:      "math",
 		Version:   "v1.0.0",
-		Methods: []*core.FFIMethod{
+		Methods: []*fftypes.FFIMethod{
 			{
 				Name: "sum",
-				Params: core.FFIParams{
+				Params: fftypes.FFIParams{
 					{
 						Name:   "x",
 						Schema: fftypes.JSONAnyPtr(`{"type": "integer"}`),
@@ -50,7 +50,7 @@ func testFFI() *core.FFI {
 						Schema: fftypes.JSONAnyPtr(`{"type": "integer"}`),
 					},
 				},
-				Returns: core.FFIParams{
+				Returns: fftypes.FFIParams{
 					{
 						Name:   "result",
 						Schema: fftypes.JSONAnyPtr(`{"type": "integer"}`),
@@ -58,12 +58,12 @@ func testFFI() *core.FFI {
 				},
 			},
 		},
-		Events: []*core.FFIEvent{
+		Events: []*fftypes.FFIEvent{
 			{
 				ID: fftypes.NewUUID(),
-				FFIEventDefinition: core.FFIEventDefinition{
+				FFIEventDefinition: fftypes.FFIEventDefinition{
 					Name: "event1",
-					Params: core.FFIParams{
+					Params: fftypes.FFIParams{
 						{
 							Name:   "result",
 							Schema: fftypes.JSONAnyPtr(`{"type": "integer"}`),
@@ -80,7 +80,7 @@ func testContractAPI() *core.ContractAPI {
 		ID:        fftypes.NewUUID(),
 		Namespace: "ns1",
 		Name:      "math",
-		Interface: &core.FFIReference{
+		Interface: &fftypes.FFIReference{
 			ID: fftypes.NewUUID(),
 		},
 		Location: fftypes.JSONAnyPtr(""),
diff --git a/internal/definitions/sender_contracts_test.go b/internal/definitions/sender_contracts_test.go
index 1cb017b42..203b2829c 100644
--- a/internal/definitions/sender_contracts_test.go
+++ b/internal/definitions/sender_contracts_test.go
@@ -21,6 +21,7 @@ import (
 	"fmt"
 	"testing"
 
+	"github.com/hyperledger/firefly-common/pkg/fftypes"
 	"github.com/hyperledger/firefly/mocks/broadcastmocks"
 	"github.com/hyperledger/firefly/mocks/contractmocks"
 	"github.com/hyperledger/firefly/mocks/identitymanagermocks"
@@ -35,9 +36,9 @@ func TestDefineFFIResolveFail(t *testing.T) {
 	defer cancel()
 	ds.multiparty = true
 
-	ffi := &core.FFI{
-		Methods: []*core.FFIMethod{{}},
-		Events:  []*core.FFIEvent{{}},
+	ffi := &fftypes.FFI{
+		Methods: []*fftypes.FFIMethod{{}},
+		Events:  []*fftypes.FFIEvent{{}},
 	}
 
 	mcm := ds.contracts.(*contractmocks.Manager)
@@ -54,7 +55,7 @@ func TestDefineFFIFail(t *testing.T) {
 	defer cancel()
 	ds.multiparty = true
 
-	ffi := &core.FFI{}
+	ffi := &fftypes.FFI{}
 
 	mcm := ds.contracts.(*contractmocks.Manager)
 	mcm.On("ResolveFFI", context.Background(), ffi).Return(nil)
@@ -74,7 +75,7 @@ func TestDefineFFIOk(t *testing.T) {
 	defer cancel()
 	ds.multiparty = true
 
-	ffi := &core.FFI{}
+	ffi := &fftypes.FFI{}
 
 	mcm := ds.contracts.(*contractmocks.Manager)
 	mcm.On("ResolveFFI", context.Background(), ffi).Return(nil)
@@ -101,7 +102,7 @@ func TestDefineFFINonMultiparty(t *testing.T) {
 	defer cancel()
 	dh := ds.handler
 
-	ffi := &core.FFI{}
+	ffi := &fftypes.FFI{}
 
 	mcm := dh.contracts.(*contractmocks.Manager)
 	mcm.On("ResolveFFI", context.Background(), ffi).Return(fmt.Errorf("pop"))
diff --git a/internal/txcommon/event_enrich_test.go b/internal/txcommon/event_enrich_test.go
index 0af31defd..e4f860e12 100644
--- a/internal/txcommon/event_enrich_test.go
+++ b/internal/txcommon/event_enrich_test.go
@@ -262,7 +262,7 @@ func TestEnrichContractInterfaceSubmitted(t *testing.T) {
 	ev1 := fftypes.NewUUID()
 
 	// Setup enrichment
-	mdi.On("GetFFIByID", mock.Anything, "ns1", ref1).Return(&core.FFI{
+	mdi.On("GetFFIByID", mock.Anything, "ns1", ref1).Return(&fftypes.FFI{
 		ID: ref1,
 	}, nil)
 
diff --git a/pkg/core/contract_listener_test.go b/pkg/core/contract_listener_test.go
index d086644b9..fbbaf88ca 100644
--- a/pkg/core/contract_listener_test.go
+++ b/pkg/core/contract_listener_test.go
@@ -49,11 +49,11 @@ func TestFFISerializedEventScanError(t *testing.T) {
 
 func TestFFISerializedEventValue(t *testing.T) {
 	params := &FFISerializedEvent{
-		FFIEventDefinition: FFIEventDefinition{
+		FFIEventDefinition: fftypes.FFIEventDefinition{
 			Name:        "event1",
 			Description: "a super event",
-			Params: FFIParams{
-				&FFIParam{Name: "details", Schema: fftypes.JSONAnyPtr(`{"type": "integer", "details": {"type": "uint256"}}`)},
+			Params: fftypes.FFIParams{
+				&fftypes.FFIParam{Name: "details", Schema: fftypes.JSONAnyPtr(`{"type": "integer", "details": {"type": "uint256"}}`)},
 			},
 		},
 	}
diff --git a/pkg/core/ffi_param_validator_test.go b/pkg/core/ffi_param_validator_test.go
deleted file mode 100644
index a0ee719ef..000000000
--- a/pkg/core/ffi_param_validator_test.go
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright © 2022 Kaleido, Inc.
-//
-// SPDX-License-Identifier: Apache-2.0
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package core
-
-import (
-	"testing"
-
-	"github.com/santhosh-tekuri/jsonschema/v5"
-	"github.com/stretchr/testify/assert"
-)
-
-func TestGetBaseFFIParamValidator(t *testing.T) {
-	c := NewFFISchemaCompiler()
-	assert.NotNil(t, c)
-}
-func TestBaseFFIParamValidatorCompile(t *testing.T) {
-	v := BaseFFIParamValidator{}
-	c, err := v.Compile(jsonschema.CompilerContext{}, map[string]interface{}{})
-	assert.Nil(t, c)
-	assert.NoError(t, err)
-}
diff --git a/pkg/core/ffi_test.go b/pkg/core/ffi_test.go
deleted file mode 100644
index 7a0641158..000000000
--- a/pkg/core/ffi_test.go
+++ /dev/null
@@ -1,150 +0,0 @@
-// Copyright © 2021 Kaleido, Inc.
-//
-// SPDX-License-Identifier: Apache-2.0
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package core
-
-import (
-	"context"
-	"testing"
-
-	"github.com/hyperledger/firefly-common/pkg/fftypes"
-	"github.com/stretchr/testify/assert"
-)
-
-func TestValidateFFI(t *testing.T) {
-	ffi := &FFI{
-		Name:      "math",
-		Namespace: "default",
-		Version:   "v1.0.0",
-		Methods: []*FFIMethod{
-			{
-				Name: "sum",
-				Params: []*FFIParam{
-					{
-						Name:   "x",
-						Schema: fftypes.JSONAnyPtr(`{"type": "integer"}, "details": {"type": "uint256"}`),
-					},
-					{
-						Name:   "y",
-						Schema: fftypes.JSONAnyPtr(`{"type": "integer"}, "details": {"type": "uint256"}`),
-					},
-				},
-				Returns: []*FFIParam{
-					{
-						Name:   "z",
-						Schema: fftypes.JSONAnyPtr(`{"type": "integer"}, "details": {"type": "uint256"}`),
-					},
-				},
-			},
-		},
-		Events: []*FFIEvent{
-			{
-				FFIEventDefinition: FFIEventDefinition{
-					Name: "sum",
-					Params: []*FFIParam{
-						{
-							Name:   "z",
-							Schema: fftypes.JSONAnyPtr(`{"type": "integer"}, "details": {"type": "uint256"}`),
-						},
-					},
-				},
-			},
-		},
-	}
-	err := ffi.Validate(context.Background(), true)
-	assert.NoError(t, err)
-}
-
-func TestValidateFFIBadVersion(t *testing.T) {
-	ffi := &FFI{
-		Name:      "math",
-		Namespace: "default",
-		Version:   "*(&!$%^)",
-	}
-	err := ffi.Validate(context.Background(), true)
-	assert.Regexp(t, "FF00140", err)
-}
-
-func TestValidateFFIBadName(t *testing.T) {
-	ffi := &FFI{
-		Name:      "(*%&#%)",
-		Namespace: "default",
-		Version:   "v1.0.0",
-	}
-	err := ffi.Validate(context.Background(), true)
-	assert.Regexp(t, "FF00140", err)
-}
-
-func TestValidateFFIBadNamespace(t *testing.T) {
-	ffi := &FFI{
-		Name:      "math",
-		Namespace: "",
-		Version:   "v1.0.0",
-	}
-	err := ffi.Validate(context.Background(), true)
-	assert.Regexp(t, "FF00140", err)
-}
-
-func TestFFIParamsScan(t *testing.T) {
-	params := &FFIParams{}
-	err := params.Scan([]byte(`[{"name": "x", "type": "integer", "internalType": "uint256"}]`))
-	assert.NoError(t, err)
-}
-
-func TestFFIParamsScanString(t *testing.T) {
-	params := &FFIParams{}
-	err := params.Scan(`[{"name": "x", "type": "integer", "internalType": "uint256"}]`)
-	assert.NoError(t, err)
-}
-
-func TestFFIParamsScanNil(t *testing.T) {
-	params := &FFIParams{}
-	err := params.Scan(nil)
-	assert.Nil(t, err)
-}
-
-func TestFFIParamsScanError(t *testing.T) {
-	params := &FFIParams{}
-	err := params.Scan(map[string]interface{}{"type": "not supported for scanning FFIParams"})
-	assert.Regexp(t, "FF00105", err)
-}
-
-func TestFFIParamsValue(t *testing.T) {
-	params := &FFIParams{
-		&FFIParam{
-			Name:   "x",
-			Schema: fftypes.JSONAnyPtr(`{"type": "integer", "details": {"type": "uint256"}}`),
-		},
-	}
-
-	val, err := params.Value()
-	assert.NoError(t, err)
-	assert.Equal(t, []byte(`[{"name":"x","schema":{"type":"integer","details":{"type":"uint256"}}}]`), val)
-}
-
-func TestFFITopic(t *testing.T) {
-	ffi := &FFI{
-		Namespace: "ns1",
-	}
-	assert.Equal(t, "01a982a7251400a7ec64fccce6febee3942a56e37967fa2ba26d7d6f43523c82", ffi.Topic())
-}
-
-func TestFFISetBroadCastMessage(t *testing.T) {
-	msgID := fftypes.NewUUID()
-	ffi := &FFI{}
-	ffi.SetBroadcastMessage(msgID)
-	assert.Equal(t, ffi.Message, msgID)
-}
diff --git a/pkg/core/validations_test.go b/pkg/core/validations_test.go
deleted file mode 100644
index 6c5fa6b0f..000000000
--- a/pkg/core/validations_test.go
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright © 2021 Kaleido, Inc.
-//
-// SPDX-License-Identifier: Apache-2.0
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package core
-
-import (
-	"context"
-	"testing"
-
-	"github.com/stretchr/testify/assert"
-)
-
-func TestValidateFFNameField(t *testing.T) {
-
-	err := ValidateFFNameField(context.Background(), "_badstart", "badField")
-	assert.Regexp(t, "FF00140.*badField", err)
-
-	err = ValidateFFNameField(context.Background(), "badend_", "badField")
-	assert.Regexp(t, "FF00140.*badField", err)
-
-	err = ValidateFFNameField(context.Background(), "0123456789_123456789-123456789.123456789-123456789_1234567890123", "badField")
-	assert.NoError(t, err)
-
-	err = ValidateFFNameField(context.Background(), "0123456789_123456789-123456789.123456789-123456789_12345678901234", "badField")
-	assert.Regexp(t, "FF00140.*badField", err)
-
-	err = ValidateFFNameField(context.Background(), "af34658e-a728-4b21-b9cf-8451f07be065", "badField")
-	assert.NoError(t, err)
-
-	err = ValidateFFNameFieldNoUUID(context.Background(), "af34658e-a728-4b21-b9cf-8451f07be065", "badField")
-	assert.Regexp(t, "FF00141.*badField", err)
-
-}
-
-func TestValidateLength(t *testing.T) {
-
-	err := ValidateLength(context.Background(), "long string", "test", 5)
-	assert.Regexp(t, "FF00135.*test", err)
-
-	err = ValidateLength(context.Background(), "short string", "test", 50)
-	assert.NoError(t, err)
-
-}
diff --git a/test/e2e/ethereum_contract_test.go b/test/e2e/ethereum_contract_test.go
index cd549c514..b5d7db5c5 100644
--- a/test/e2e/ethereum_contract_test.go
+++ b/test/e2e/ethereum_contract_test.go
@@ -47,11 +47,11 @@ type simpleStorageBody struct {
 	NewValue string `json:"newValue"`
 }
 
-func simpleStorageFFIChanged() *core.FFIEvent {
-	return &core.FFIEvent{
-		FFIEventDefinition: core.FFIEventDefinition{
+func simpleStorageFFIChanged() *fftypes.FFIEvent {
+	return &fftypes.FFIEvent{
+		FFIEventDefinition: fftypes.FFIEventDefinition{
 			Name: "Changed",
-			Params: core.FFIParams{
+			Params: fftypes.FFIParams{
 				{
 					Name:   "_from",
 					Schema: fftypes.JSONAnyPtr(`{"type": "string", "details": {"type": "address", "indexed": true}}`),
@@ -65,38 +65,38 @@ func simpleStorageFFIChanged() *core.FFIEvent {
 	}
 }
 
-func simpleStorageFFI() *core.FFI {
-	return &core.FFI{
+func simpleStorageFFI() *fftypes.FFI {
+	return &fftypes.FFI{
 		Name:    "SimpleStorage",
 		Version: contractVersion,
-		Methods: []*core.FFIMethod{
+		Methods: []*fftypes.FFIMethod{
 			simpleStorageFFISet(),
 			simpleStorageFFIGet(),
 		},
-		Events: []*core.FFIEvent{
+		Events: []*fftypes.FFIEvent{
 			simpleStorageFFIChanged(),
 		},
 	}
 }
 
-func simpleStorageFFISet() *core.FFIMethod {
-	return &core.FFIMethod{
+func simpleStorageFFISet() *fftypes.FFIMethod {
+	return &fftypes.FFIMethod{
 		Name: "set",
-		Params: core.FFIParams{
+		Params: fftypes.FFIParams{
 			{
 				Name:   "newValue",
 				Schema: fftypes.JSONAnyPtr(`{"type": "integer", "details": {"type": "uint256"}}`),
 			},
 		},
-		Returns: core.FFIParams{},
+		Returns: fftypes.FFIParams{},
 	}
 }
 
-func simpleStorageFFIGet() *core.FFIMethod {
-	return &core.FFIMethod{
+func simpleStorageFFIGet() *fftypes.FFIMethod {
+	return &fftypes.FFIMethod{
 		Name:   "get",
-		Params: core.FFIParams{},
-		Returns: core.FFIParams{
+		Params: fftypes.FFIParams{},
+		Returns: fftypes.FFIParams{
 			{
 				Name:   "output",
 				Schema: fftypes.JSONAnyPtr(`{"type": "integer", "details": {"type": "uint256"}}`),
@@ -199,7 +199,7 @@ func (suite *EthereumContractTestSuite) TestFFIInvokeMethod() {
 
 	received1 := wsReader(suite.testState.ws1, true)
 
-	ffiReference := &core.FFIReference{
+	ffiReference := &fftypes.FFIReference{
 		ID: suite.interfaceID,
 	}
 	listener := CreateFFIContractListener(suite.T(), suite.testState.client1, ffiReference, "Changed", &fftypes.JSONObject{
@@ -259,7 +259,7 @@ func (suite *EthereumContractTestSuite) TestContractAPIMethod() {
 	received1 := wsReader(suite.testState.ws1, true)
 	APIName := fftypes.NewUUID().String()
 
-	ffiReference := &core.FFIReference{
+	ffiReference := &fftypes.FFIReference{
 		ID: suite.interfaceID,
 	}
 
diff --git a/test/e2e/fabric_contract_test.go b/test/e2e/fabric_contract_test.go
index 3c99c42c3..366035783 100644
--- a/test/e2e/fabric_contract_test.go
+++ b/test/e2e/fabric_contract_test.go
@@ -45,35 +45,35 @@ type createAssetBody struct {
 	Args    []string          `json:"args"`
 }
 
-var assetCreatedEvent = &core.FFIEvent{
-	FFIEventDefinition: core.FFIEventDefinition{
+var assetCreatedEvent = &fftypes.FFIEvent{
+	FFIEventDefinition: fftypes.FFIEventDefinition{
 		Name: "AssetCreated",
 	},
 }
 
-func assetManagerCreateAsset() *core.FFIMethod {
-	return &core.FFIMethod{
+func assetManagerCreateAsset() *fftypes.FFIMethod {
+	return &fftypes.FFIMethod{
 		Name: "CreateAsset",
-		Params: core.FFIParams{
+		Params: fftypes.FFIParams{
 			{
 				Name:   "name",
 				Schema: fftypes.JSONAnyPtr(`{"type": "string"}`),
 			},
 		},
-		Returns: core.FFIParams{},
+		Returns: fftypes.FFIParams{},
 	}
 }
 
-func assetManagerGetAsset() *core.FFIMethod {
-	return &core.FFIMethod{
+func assetManagerGetAsset() *fftypes.FFIMethod {
+	return &fftypes.FFIMethod{
 		Name: "GetAsset",
-		Params: core.FFIParams{
+		Params: fftypes.FFIParams{
 			{
 				Name:   "name",
 				Schema: fftypes.JSONAnyPtr(`{"type": "string"}`),
 			},
 		},
-		Returns: core.FFIParams{
+		Returns: fftypes.FFIParams{
 			{
 				Name:   "name",
 				Schema: fftypes.JSONAnyPtr(`{"type": "string"}`),
diff --git a/test/e2e/restclient_test.go b/test/e2e/restclient_test.go
index 953a6eb0b..0decd47d7 100644
--- a/test/e2e/restclient_test.go
+++ b/test/e2e/restclient_test.go
@@ -624,7 +624,7 @@ func GetTokenBalance(t *testing.T, client *resty.Client, poolID *fftypes.UUID, t
 	return accounts[0]
 }
 
-func CreateContractListener(t *testing.T, client *resty.Client, event *core.FFIEvent, location *fftypes.JSONObject) *core.ContractListener {
+func CreateContractListener(t *testing.T, client *resty.Client, event *fftypes.FFIEvent, location *fftypes.JSONObject) *core.ContractListener {
 	body := core.ContractListenerInput{
 		ContractListener: core.ContractListener{
 			Location: fftypes.JSONAnyPtr(location.String()),
@@ -645,7 +645,7 @@ func CreateContractListener(t *testing.T, client *resty.Client, event *core.FFIE
 	return &sub
 }
 
-func CreateFFIContractListener(t *testing.T, client *resty.Client, ffiReference *core.FFIReference, eventPath string, location *fftypes.JSONObject) *core.ContractListener {
+func CreateFFIContractListener(t *testing.T, client *resty.Client, ffiReference *fftypes.FFIReference, eventPath string, location *fftypes.JSONObject) *core.ContractListener {
 	body := core.ContractListenerInput{
 		ContractListener: core.ContractListener{
 			Location:  fftypes.JSONAnyPtr(location.String()),
@@ -719,7 +719,7 @@ func QueryContractMethod(t *testing.T, client *resty.Client, req *core.ContractC
 	return res, err
 }
 
-func CreateFFI(t *testing.T, client *resty.Client, ffi *core.FFI) (interface{}, error) {
+func CreateFFI(t *testing.T, client *resty.Client, ffi *fftypes.FFI) (interface{}, error) {
 	var res interface{}
 	path := urlContractInterface
 	resp, err := client.R().
@@ -732,7 +732,7 @@ func CreateFFI(t *testing.T, client *resty.Client, ffi *core.FFI) (interface{},
 	return res, err
 }
 
-func CreateContractAPI(t *testing.T, client *resty.Client, name string, FFIReference *core.FFIReference, location *fftypes.JSONAny) (interface{}, error) {
+func CreateContractAPI(t *testing.T, client *resty.Client, name string, FFIReference *fftypes.FFIReference, location *fftypes.JSONAny) (interface{}, error) {
 	apiReqBody := &core.ContractAPI{
 		Name:      name,
 		Interface: FFIReference,