Repository: hyperledger/firefly
Pull request #1123 (2022-12-28 21:38:00)

Add ability to bind a contract interface to a token pool
Part of [FIR-16](https://github.com/hyperledger/firefly-fir/pull/16).
Depends on https://github.com/hyperledger/firefly-tokens-erc20-erc721/pull/106.

_Discussion about the overall architecture/usefulness of this proposed feature should be directed to [FIR-16](https://github.com/hyperledger/firefly-fir/pull/16), while discussion here should focus on specific technical details of the current proposed approach._

This adds the ability for FireFly to provide ABI details to a token connector, making it easier for the token connector to know the shape of the underlying contract it is dealing with (vs. having to probe/guess at the methods available).

**Notes**
* This feature is only useful to users who have significant knowledge of the underlying token contract that sits on the other side of their token connector.
* It's likely to be most useful when creating a pool against a pre-deployed contract - ie when passing `config.address`. Worth mentioning that FireFly doesn't really "know" you can create pools against pre-deployed contracts (as `config` is opaque), but it's up to the user to understand the combination of features that are useful here.
* Theoretically you could create a factory contract that deploys custom ERC20/ERC721 instances, and then you'd need to pass `interface` on every pool creation even while relying on the factory sitting on the other side of the token connector to do the deploy. Seems like a less useful combo, but calling it out here for completeness.


Patch:
diff --git a/db/migrations/postgres/000104_add_tokenpool_interface.down.sql b/db/migrations/postgres/000104_add_tokenpool_interface.down.sql
new file mode 100644
index 000000000..5904ab788
--- /dev/null
+++ b/db/migrations/postgres/000104_add_tokenpool_interface.down.sql
@@ -0,0 +1,5 @@
+BEGIN;
+ALTER TABLE tokenpool DROP COLUMN interface;
+ALTER TABLE tokenpool DROP COLUMN interface_format;
+ALTER TABLE tokenpool DROP COLUMN methods;
+COMMIT;
diff --git a/db/migrations/postgres/000104_add_tokenpool_interface.up.sql b/db/migrations/postgres/000104_add_tokenpool_interface.up.sql
new file mode 100644
index 000000000..e9cde90ab
--- /dev/null
+++ b/db/migrations/postgres/000104_add_tokenpool_interface.up.sql
@@ -0,0 +1,5 @@
+BEGIN;
+ALTER TABLE tokenpool ADD COLUMN interface UUID;
+ALTER TABLE tokenpool ADD COLUMN interface_format VARCHAR(64);
+ALTER TABLE tokenpool ADD COLUMN methods TEXT;
+COMMIT;
diff --git a/db/migrations/sqlite/000104_add_tokenpool_interface.down.sql b/db/migrations/sqlite/000104_add_tokenpool_interface.down.sql
new file mode 100644
index 000000000..5fb099251
--- /dev/null
+++ b/db/migrations/sqlite/000104_add_tokenpool_interface.down.sql
@@ -0,0 +1,3 @@
+ALTER TABLE tokenpool DROP COLUMN interface;
+ALTER TABLE tokenpool DROP COLUMN interface_format;
+ALTER TABLE tokenpool DROP COLUMN methods;
diff --git a/db/migrations/sqlite/000104_add_tokenpool_interface.up.sql b/db/migrations/sqlite/000104_add_tokenpool_interface.up.sql
new file mode 100644
index 000000000..9df94d535
--- /dev/null
+++ b/db/migrations/sqlite/000104_add_tokenpool_interface.up.sql
@@ -0,0 +1,3 @@
+ALTER TABLE tokenpool ADD COLUMN interface UUID;
+ALTER TABLE tokenpool ADD COLUMN interface_format VARCHAR(64);
+ALTER TABLE tokenpool ADD COLUMN methods TEXT;
diff --git a/docs/reference/types/tokenpool.md b/docs/reference/types/tokenpool.md
index 8dfd14c52..ba20c576d 100644
--- a/docs/reference/types/tokenpool.md
+++ b/docs/reference/types/tokenpool.md
@@ -67,6 +67,9 @@ nav_order: 10
 | `config` | Input only field, with token connector specific configuration of the pool, such as an existing Ethereum address and block number to used to index the pool. See your chosen token connector documentation for details | [`JSONObject`](simpletypes#jsonobject) |
 | `info` | Token connector specific information about the pool. See your chosen token connector documentation for details | [`JSONObject`](simpletypes#jsonobject) |
 | `tx` | Reference to the FireFly transaction used to create and broadcast this pool to the network | [`TransactionRef`](#transactionref) |
+| `interface` | A reference to an existing FFI, containing pre-registered type information for the token contract | [`FFIReference`](#ffireference) |
+| `interfaceFormat` | The interface encoding format supported by the connector for this token pool | `FFEnum`:<br/>`"abi"`<br/>`"ffi"` |
+| `methods` | The method definitions resolved by the token connector to be used by each token operation | [`JSONAny`](simpletypes#jsonany) |
 
 ## TransactionRef
 
@@ -76,3 +79,12 @@ nav_order: 10
 | `id` | The UUID of the FireFly transaction | [`UUID`](simpletypes#uuid) |
 
 
+## FFIReference
+
+| Field Name | Description | Type |
+|------------|-------------|------|
+| `id` | The UUID of the FireFly interface | [`UUID`](simpletypes#uuid) |
+| `name` | The name of the FireFly interface | `string` |
+| `version` | The version of the FireFly interface | `string` |
+
+
diff --git a/docs/swagger/swagger.yaml b/docs/swagger/swagger.yaml
index c0640c9f5..11d18379e 100644
--- a/docs/swagger/swagger.yaml
+++ b/docs/swagger/swagger.yaml
@@ -26238,6 +26238,16 @@ paths:
         name: id
         schema:
           type: string
+      - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^'
+        in: query
+        name: interface
+        schema:
+          type: string
+      - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^'
+        in: query
+        name: interfaceformat
+        schema:
+          type: string
       - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^'
         in: query
         name: locator
@@ -26349,6 +26359,28 @@ paths:
                       description: Token connector specific information about the
                         pool. See your chosen token connector documentation for details
                       type: object
+                    interface:
+                      description: A reference to an existing FFI, containing pre-registered
+                        type information for the token contract
+                      properties:
+                        id:
+                          description: The UUID of the FireFly interface
+                          format: uuid
+                          type: string
+                        name:
+                          description: The name of the FireFly interface
+                          type: string
+                        version:
+                          description: The version of the FireFly interface
+                          type: string
+                      type: object
+                    interfaceFormat:
+                      description: The interface encoding format supported by the
+                        connector for this token pool
+                      enum:
+                      - abi
+                      - ffi
+                      type: string
                     key:
                       description: The signing key used to create the token pool.
                         On input for token connectors that support on-chain deployment
@@ -26364,6 +26396,9 @@ paths:
                         the network to index this pool
                       format: uuid
                       type: string
+                    methods:
+                      description: The method definitions resolved by the token connector
+                        to be used by each token operation
                     name:
                       description: The name of the token pool. Note the name is not
                         validated against the description of the token on the blockchain
@@ -26458,6 +26493,21 @@ paths:
                   description: An optional identifier to allow idempotent submission
                     of requests. Stored on the transaction uniquely within a namespace
                   type: string
+                interface:
+                  description: A reference to an existing FFI, containing pre-registered
+                    type information for the token contract
+                  properties:
+                    id:
+                      description: The UUID of the FireFly interface
+                      format: uuid
+                      type: string
+                    name:
+                      description: The name of the FireFly interface
+                      type: string
+                    version:
+                      description: The version of the FireFly interface
+                      type: string
+                  type: object
                 key:
                   description: The signing key used to create the token pool. On input
                     for token connectors that support on-chain deployment of new tokens
@@ -26509,6 +26559,28 @@ paths:
                     description: Token connector specific information about the pool.
                       See your chosen token connector documentation for details
                     type: object
+                  interface:
+                    description: A reference to an existing FFI, containing pre-registered
+                      type information for the token contract
+                    properties:
+                      id:
+                        description: The UUID of the FireFly interface
+                        format: uuid
+                        type: string
+                      name:
+                        description: The name of the FireFly interface
+                        type: string
+                      version:
+                        description: The version of the FireFly interface
+                        type: string
+                    type: object
+                  interfaceFormat:
+                    description: The interface encoding format supported by the connector
+                      for this token pool
+                    enum:
+                    - abi
+                    - ffi
+                    type: string
                   key:
                     description: The signing key used to create the token pool. On
                       input for token connectors that support on-chain deployment
@@ -26524,6 +26596,9 @@ paths:
                       the network to index this pool
                     format: uuid
                     type: string
+                  methods:
+                    description: The method definitions resolved by the token connector
+                      to be used by each token operation
                   name:
                     description: The name of the token pool. Note the name is not
                       validated against the description of the token on the blockchain
@@ -26594,6 +26669,28 @@ paths:
                     description: Token connector specific information about the pool.
                       See your chosen token connector documentation for details
                     type: object
+                  interface:
+                    description: A reference to an existing FFI, containing pre-registered
+                      type information for the token contract
+                    properties:
+                      id:
+                        description: The UUID of the FireFly interface
+                        format: uuid
+                        type: string
+                      name:
+                        description: The name of the FireFly interface
+                        type: string
+                      version:
+                        description: The version of the FireFly interface
+                        type: string
+                    type: object
+                  interfaceFormat:
+                    description: The interface encoding format supported by the connector
+                      for this token pool
+                    enum:
+                    - abi
+                    - ffi
+                    type: string
                   key:
                     description: The signing key used to create the token pool. On
                       input for token connectors that support on-chain deployment
@@ -26609,6 +26706,9 @@ paths:
                       the network to index this pool
                     format: uuid
                     type: string
+                  methods:
+                    description: The method definitions resolved by the token connector
+                      to be used by each token operation
                   name:
                     description: The name of the token pool. Note the name is not
                       validated against the description of the token on the blockchain
@@ -26709,6 +26809,28 @@ paths:
                     description: Token connector specific information about the pool.
                       See your chosen token connector documentation for details
                     type: object
+                  interface:
+                    description: A reference to an existing FFI, containing pre-registered
+                      type information for the token contract
+                    properties:
+                      id:
+                        description: The UUID of the FireFly interface
+                        format: uuid
+                        type: string
+                      name:
+                        description: The name of the FireFly interface
+                        type: string
+                      version:
+                        description: The version of the FireFly interface
+                        type: string
+                    type: object
+                  interfaceFormat:
+                    description: The interface encoding format supported by the connector
+                      for this token pool
+                    enum:
+                    - abi
+                    - ffi
+                    type: string
                   key:
                     description: The signing key used to create the token pool. On
                       input for token connectors that support on-chain deployment
@@ -26724,6 +26846,9 @@ paths:
                       the network to index this pool
                     format: uuid
                     type: string
+                  methods:
+                    description: The method definitions resolved by the token connector
+                      to be used by each token operation
                   name:
                     description: The name of the token pool. Note the name is not
                       validated against the description of the token on the blockchain
@@ -33532,6 +33657,16 @@ paths:
         name: id
         schema:
           type: string
+      - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^'
+        in: query
+        name: interface
+        schema:
+          type: string
+      - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^'
+        in: query
+        name: interfaceformat
+        schema:
+          type: string
       - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^'
         in: query
         name: locator
@@ -33643,6 +33778,28 @@ paths:
                       description: Token connector specific information about the
                         pool. See your chosen token connector documentation for details
                       type: object
+                    interface:
+                      description: A reference to an existing FFI, containing pre-registered
+                        type information for the token contract
+                      properties:
+                        id:
+                          description: The UUID of the FireFly interface
+                          format: uuid
+                          type: string
+                        name:
+                          description: The name of the FireFly interface
+                          type: string
+                        version:
+                          description: The version of the FireFly interface
+                          type: string
+                      type: object
+                    interfaceFormat:
+                      description: The interface encoding format supported by the
+                        connector for this token pool
+                      enum:
+                      - abi
+                      - ffi
+                      type: string
                     key:
                       description: The signing key used to create the token pool.
                         On input for token connectors that support on-chain deployment
@@ -33658,6 +33815,9 @@ paths:
                         the network to index this pool
                       format: uuid
                       type: string
+                    methods:
+                      description: The method definitions resolved by the token connector
+                        to be used by each token operation
                     name:
                       description: The name of the token pool. Note the name is not
                         validated against the description of the token on the blockchain
@@ -33745,6 +33905,21 @@ paths:
                   description: An optional identifier to allow idempotent submission
                     of requests. Stored on the transaction uniquely within a namespace
                   type: string
+                interface:
+                  description: A reference to an existing FFI, containing pre-registered
+                    type information for the token contract
+                  properties:
+                    id:
+                      description: The UUID of the FireFly interface
+                      format: uuid
+                      type: string
+                    name:
+                      description: The name of the FireFly interface
+                      type: string
+                    version:
+                      description: The version of the FireFly interface
+                      type: string
+                  type: object
                 key:
                   description: The signing key used to create the token pool. On input
                     for token connectors that support on-chain deployment of new tokens
@@ -33796,6 +33971,28 @@ paths:
                     description: Token connector specific information about the pool.
                       See your chosen token connector documentation for details
                     type: object
+                  interface:
+                    description: A reference to an existing FFI, containing pre-registered
+                      type information for the token contract
+                    properties:
+                      id:
+                        description: The UUID of the FireFly interface
+                        format: uuid
+                        type: string
+                      name:
+                        description: The name of the FireFly interface
+                        type: string
+                      version:
+                        description: The version of the FireFly interface
+                        type: string
+                    type: object
+                  interfaceFormat:
+                    description: The interface encoding format supported by the connector
+                      for this token pool
+                    enum:
+                    - abi
+                    - ffi
+                    type: string
                   key:
                     description: The signing key used to create the token pool. On
                       input for token connectors that support on-chain deployment
@@ -33811,6 +34008,9 @@ paths:
                       the network to index this pool
                     format: uuid
                     type: string
+                  methods:
+                    description: The method definitions resolved by the token connector
+                      to be used by each token operation
                   name:
                     description: The name of the token pool. Note the name is not
                       validated against the description of the token on the blockchain
@@ -33881,6 +34081,28 @@ paths:
                     description: Token connector specific information about the pool.
                       See your chosen token connector documentation for details
                     type: object
+                  interface:
+                    description: A reference to an existing FFI, containing pre-registered
+                      type information for the token contract
+                    properties:
+                      id:
+                        description: The UUID of the FireFly interface
+                        format: uuid
+                        type: string
+                      name:
+                        description: The name of the FireFly interface
+                        type: string
+                      version:
+                        description: The version of the FireFly interface
+                        type: string
+                    type: object
+                  interfaceFormat:
+                    description: The interface encoding format supported by the connector
+                      for this token pool
+                    enum:
+                    - abi
+                    - ffi
+                    type: string
                   key:
                     description: The signing key used to create the token pool. On
                       input for token connectors that support on-chain deployment
@@ -33896,6 +34118,9 @@ paths:
                       the network to index this pool
                     format: uuid
                     type: string
+                  methods:
+                    description: The method definitions resolved by the token connector
+                      to be used by each token operation
                   name:
                     description: The name of the token pool. Note the name is not
                       validated against the description of the token on the blockchain
@@ -33989,6 +34214,28 @@ paths:
                     description: Token connector specific information about the pool.
                       See your chosen token connector documentation for details
                     type: object
+                  interface:
+                    description: A reference to an existing FFI, containing pre-registered
+                      type information for the token contract
+                    properties:
+                      id:
+                        description: The UUID of the FireFly interface
+                        format: uuid
+                        type: string
+                      name:
+                        description: The name of the FireFly interface
+                        type: string
+                      version:
+                        description: The version of the FireFly interface
+                        type: string
+                    type: object
+                  interfaceFormat:
+                    description: The interface encoding format supported by the connector
+                      for this token pool
+                    enum:
+                    - abi
+                    - ffi
+                    type: string
                   key:
                     description: The signing key used to create the token pool. On
                       input for token connectors that support on-chain deployment
@@ -34004,6 +34251,9 @@ paths:
                       the network to index this pool
                     format: uuid
                     type: string
+                  methods:
+                    description: The method definitions resolved by the token connector
+                      to be used by each token operation
                   name:
                     description: The name of the token pool. Note the name is not
                       validated against the description of the token on the blockchain
diff --git a/internal/assets/manager.go b/internal/assets/manager.go
index 1d292ce27..b16ddb606 100644
--- a/internal/assets/manager.go
+++ b/internal/assets/manager.go
@@ -1,4 +1,4 @@
-// Copyright © 2022 Kaleido, Inc.
+// Copyright © 2023 Kaleido, Inc.
 //
 // SPDX-License-Identifier: Apache-2.0
 //
@@ -23,6 +23,7 @@ import (
 	"github.com/hyperledger/firefly-common/pkg/fftypes"
 	"github.com/hyperledger/firefly-common/pkg/i18n"
 	"github.com/hyperledger/firefly/internal/broadcast"
+	"github.com/hyperledger/firefly/internal/contracts"
 	"github.com/hyperledger/firefly/internal/coremsgs"
 	"github.com/hyperledger/firefly/internal/identity"
 	"github.com/hyperledger/firefly/internal/metrics"
@@ -43,6 +44,7 @@ type Manager interface {
 	GetTokenPools(ctx context.Context, filter ffapi.AndFilter) ([]*core.TokenPool, *ffapi.FilterResult, error)
 	GetTokenPool(ctx context.Context, connector, poolName string) (*core.TokenPool, error)
 	GetTokenPoolByNameOrID(ctx context.Context, poolNameOrID string) (*core.TokenPool, error)
+	ResolvePoolMethods(ctx context.Context, pool *core.TokenPool) error
 
 	GetTokenBalances(ctx context.Context, filter ffapi.AndFilter) ([]*core.TokenBalance, *ffapi.FilterResult, error)
 	GetTokenAccounts(ctx context.Context, filter ffapi.AndFilter) ([]*core.TokenAccount, *ffapi.FilterResult, error)
@@ -79,10 +81,11 @@ type assetManager struct {
 	tokens           map[string]tokens.Plugin
 	metrics          metrics.Manager
 	operations       operations.Manager
+	contracts        contracts.Manager
 	keyNormalization int
 }
 
-func NewAssetManager(ctx context.Context, ns, keyNormalization string, di database.Plugin, ti map[string]tokens.Plugin, im identity.Manager, sa syncasync.Bridge, bm broadcast.Manager, pm privatemessaging.Manager, mm metrics.Manager, om operations.Manager, txHelper txcommon.Helper) (Manager, error) {
+func NewAssetManager(ctx context.Context, ns, keyNormalization string, di database.Plugin, ti map[string]tokens.Plugin, im identity.Manager, sa syncasync.Bridge, bm broadcast.Manager, pm privatemessaging.Manager, mm metrics.Manager, om operations.Manager, cm contracts.Manager, txHelper txcommon.Helper) (Manager, error) {
 	if di == nil || im == nil || sa == nil || ti == nil || mm == nil || om == nil {
 		return nil, i18n.NewError(ctx, coremsgs.MsgInitializationNilDepError, "AssetManager")
 	}
@@ -99,6 +102,7 @@ func NewAssetManager(ctx context.Context, ns, keyNormalization string, di databa
 		keyNormalization: identity.ParseKeyNormalizationConfig(keyNormalization),
 		metrics:          mm,
 		operations:       om,
+		contracts:        cm,
 	}
 	om.RegisterHandler(ctx, am, []core.OpType{
 		core.OpTypeTokenCreatePool,
diff --git a/internal/assets/operations.go b/internal/assets/operations.go
index d3015fa46..5d7fb415e 100644
--- a/internal/assets/operations.go
+++ b/internal/assets/operations.go
@@ -1,4 +1,4 @@
-// Copyright © 2022 Kaleido, Inc.
+// Copyright © 2023 Kaleido, Inc.
 //
 // SPDX-License-Identifier: Apache-2.0
 //
@@ -124,11 +124,11 @@ func (am *assetManager) RunOperation(ctx context.Context, op *core.PreparedOpera
 		}
 		switch data.Transfer.Type {
 		case core.TokenTransferTypeMint:
-			return nil, false, plugin.MintTokens(ctx, op.NamespacedIDString(), data.Pool.Locator, data.Transfer)
+			return nil, false, plugin.MintTokens(ctx, op.NamespacedIDString(), data.Pool.Locator, data.Transfer, data.Pool.Methods)
 		case core.TokenTransferTypeTransfer:
-			return nil, false, plugin.TransferTokens(ctx, op.NamespacedIDString(), data.Pool.Locator, data.Transfer)
+			return nil, false, plugin.TransferTokens(ctx, op.NamespacedIDString(), data.Pool.Locator, data.Transfer, data.Pool.Methods)
 		case core.TokenTransferTypeBurn:
-			return nil, false, plugin.BurnTokens(ctx, op.NamespacedIDString(), data.Pool.Locator, data.Transfer)
+			return nil, false, plugin.BurnTokens(ctx, op.NamespacedIDString(), data.Pool.Locator, data.Transfer, data.Pool.Methods)
 		default:
 			panic(fmt.Sprintf("unknown transfer type: %v", data.Transfer.Type))
 		}
@@ -138,7 +138,7 @@ func (am *assetManager) RunOperation(ctx context.Context, op *core.PreparedOpera
 		if err != nil {
 			return nil, false, err
 		}
-		return nil, false, plugin.TokensApproval(ctx, op.NamespacedIDString(), data.Pool.Locator, data.Approval)
+		return nil, false, plugin.TokensApproval(ctx, op.NamespacedIDString(), data.Pool.Locator, data.Approval, data.Pool.Methods)
 
 	default:
 		return nil, false, i18n.NewError(ctx, coremsgs.MsgOperationDataIncorrect, op.Data)
diff --git a/internal/assets/token_pool.go b/internal/assets/token_pool.go
index 9cd1b436e..93644430c 100644
--- a/internal/assets/token_pool.go
+++ b/internal/assets/token_pool.go
@@ -1,4 +1,4 @@
-// Copyright © 2022 Kaleido, Inc.
+// Copyright © 2023 Kaleido, Inc.
 //
 // SPDX-License-Identifier: Apache-2.0
 //
@@ -49,6 +49,12 @@ func (am *assetManager) CreateTokenPool(ctx context.Context, pool *core.TokenPoo
 		pool.Connector = connector
 	}
 
+	if pool.Interface != nil {
+		if err := am.contracts.ResolveFFIReference(ctx, pool.Interface); err != nil {
+			return nil, err
+		}
+	}
+
 	var err error
 	pool.Key, err = am.identity.NormalizeSigningKey(ctx, pool.Key, am.keyNormalization)
 	if err != nil {
@@ -174,3 +180,15 @@ func (am *assetManager) GetTokenPoolByNameOrID(ctx context.Context, poolNameOrID
 	}
 	return pool, nil
 }
+
+func (am *assetManager) ResolvePoolMethods(ctx context.Context, pool *core.TokenPool) error {
+	plugin, err := am.selectTokenPlugin(ctx, pool.Connector)
+	if err == nil && pool.Interface != nil && pool.Interface.ID != nil && am.contracts != nil {
+		var methods []*fftypes.FFIMethod
+		methods, err = am.contracts.GetFFIMethods(ctx, pool.Interface.ID)
+		if err == nil {
+			pool.Methods, err = plugin.CheckInterface(ctx, pool, methods)
+		}
+	}
+	return err
+}
diff --git a/internal/contracts/manager.go b/internal/contracts/manager.go
index 0219e2543..fe7af4489 100644
--- a/internal/contracts/manager.go
+++ b/internal/contracts/manager.go
@@ -42,8 +42,11 @@ type Manager interface {
 	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)
+	GetFFIMethods(ctx context.Context, id *fftypes.UUID) ([]*fftypes.FFIMethod, error)
+	GetFFIEvents(ctx context.Context, id *fftypes.UUID) ([]*fftypes.FFIEvent, error)
 	GetFFIs(ctx context.Context, filter ffapi.AndFilter) ([]*fftypes.FFI, *ffapi.FilterResult, error)
 	ResolveFFI(ctx context.Context, ffi *fftypes.FFI) error
+	ResolveFFIReference(ctx context.Context, ref *fftypes.FFIReference) error
 
 	DeployContract(ctx context.Context, req *core.ContractDeployRequest, waitConfirm bool) (interface{}, error)
 	InvokeContract(ctx context.Context, req *core.ContractCallRequest, waitConfirm bool) (interface{}, error)
@@ -134,15 +137,30 @@ func (cm *contractManager) GetFFIByID(ctx context.Context, id *fftypes.UUID) (*f
 	return cm.database.GetFFIByID(ctx, cm.namespace, id)
 }
 
+func (cm *contractManager) GetFFIMethods(ctx context.Context, id *fftypes.UUID) ([]*fftypes.FFIMethod, error) {
+	fb := database.FFIMethodQueryFactory.NewFilter(ctx)
+	methods, _, err := cm.database.GetFFIMethods(ctx, cm.namespace, fb.Eq("interface", id))
+	return methods, err
+}
+
+func (cm *contractManager) GetFFIEvents(ctx context.Context, id *fftypes.UUID) ([]*fftypes.FFIEvent, error) {
+	fb := database.FFIMethodQueryFactory.NewFilter(ctx)
+	events, _, err := cm.database.GetFFIEvents(ctx, cm.namespace, fb.Eq("interface", id))
+	if err == nil {
+		for _, event := range events {
+			event.Signature = cm.blockchain.GenerateEventSignature(ctx, &event.FFIEventDefinition)
+		}
+	}
+	return events, err
+}
+
 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))
+	ffi.Methods, err = cm.GetFFIMethods(ctx, ffi.ID)
 	if err != nil {
 		return err
 	}
 
-	efb := database.FFIEventQueryFactory.NewFilter(ctx)
-	ffi.Events, _, err = cm.database.GetFFIEvents(ctx, cm.namespace, efb.Eq("interface", ffi.ID))
+	ffi.Events, err = cm.GetFFIEvents(ctx, ffi.ID)
 	if err != nil {
 		return err
 	}
@@ -362,7 +380,7 @@ func (cm *contractManager) ResolveContractAPI(ctx context.Context, httpServerURL
 			}
 		}
 
-		if err := cm.resolveFFIReference(ctx, api.Interface); err != nil {
+		if err := cm.ResolveFFIReference(ctx, api.Interface); err != nil {
 			return err
 		}
 		return nil
@@ -377,7 +395,7 @@ func (cm *contractManager) ResolveContractAPI(ctx context.Context, httpServerURL
 	return nil
 }
 
-func (cm *contractManager) resolveFFIReference(ctx context.Context, ref *fftypes.FFIReference) error {
+func (cm *contractManager) ResolveFFIReference(ctx context.Context, ref *fftypes.FFIReference) error {
 	switch {
 	case ref == nil:
 		return i18n.NewError(ctx, coremsgs.MsgContractInterfaceNotFound, "")
@@ -531,7 +549,7 @@ func (cm *contractManager) validateInvokeContractRequest(ctx context.Context, re
 }
 
 func (cm *contractManager) resolveEvent(ctx context.Context, ffi *fftypes.FFIReference, eventPath string) (*core.FFISerializedEvent, error) {
-	if err := cm.resolveFFIReference(ctx, ffi); err != nil {
+	if err := cm.ResolveFFIReference(ctx, ffi); err != nil {
 		return nil, err
 	}
 	event, err := cm.database.GetFFIEvent(ctx, cm.namespace, ffi.ID, eventPath)
diff --git a/internal/coremsgs/en_error_messages.go b/internal/coremsgs/en_error_messages.go
index 99548e9c9..4d38d05b9 100644
--- a/internal/coremsgs/en_error_messages.go
+++ b/internal/coremsgs/en_error_messages.go
@@ -273,4 +273,5 @@ var (
 	MsgNonIdempotencyKeyConflictTxInsert  = ffe("FF10432", "Conflict on insert of transaction '%s'. No existing transaction matching idempotency key '%s' found", 409)
 	MsgErrorNameMustBeSet                 = ffe("FF10433", "The name of the error must be set", 400)
 	MsgContractErrorsResolveError         = ffe("FF10434", "Unable to resolve contract errors: %s", 400)
+	MsgUnknownInterfaceFormat             = ffe("FF10435", "Unknown interface format: %s", 400)
 )
diff --git a/internal/coremsgs/en_struct_descriptions.go b/internal/coremsgs/en_struct_descriptions.go
index 3090dacd5..78049c8c1 100644
--- a/internal/coremsgs/en_struct_descriptions.go
+++ b/internal/coremsgs/en_struct_descriptions.go
@@ -557,22 +557,25 @@ var (
 	TokenConnectorName = ffm("TokenConnector.name", "The name of the token connector, as configured in the FireFly core configuration file")
 
 	// TokenPool field descriptions
-	TokenPoolID        = ffm("TokenPool.id", "The UUID of the token pool")
-	TokenPoolType      = ffm("TokenPool.type", "The type of token the pool contains, such as fungible/non-fungible")
-	TokenPoolNamespace = ffm("TokenPool.namespace", "The namespace for the token pool")
-	TokenPoolName      = ffm("TokenPool.name", "The name of the token pool. Note the name is not validated against the description of the token on the blockchain")
-	TokenPoolStandard  = ffm("TokenPool.standard", "The ERC standard the token pool conforms to, as reported by the token connector")
-	TokenPoolLocator   = ffm("TokenPool.locator", "A unique identifier for the pool, as provided by the token connector")
-	TokenPoolKey       = ffm("TokenPool.key", "The signing key used to create the token pool. On input for token connectors that support on-chain deployment of new tokens (vs. only index existing ones) this determines the signing key used to create the token on-chain")
-	TokenPoolSymbol    = ffm("TokenPool.symbol", "The token symbol. If supplied on input for an existing on-chain token, this must match the on-chain information")
-	TokenPoolDecimals  = ffm("TokenPool.decimals", "Number of decimal places that this token has")
-	TokenPoolConnector = ffm("TokenPool.connector", "The name of the token connector, as specified in the FireFly core configuration file that is responsible for the token pool. Required on input when multiple token connectors are configured")
-	TokenPoolMessage   = ffm("TokenPool.message", "The UUID of the broadcast message used to inform the network to index this pool")
-	TokenPoolState     = ffm("TokenPool.state", "The current state of the token pool")
-	TokenPoolCreated   = ffm("TokenPool.created", "The creation time of the pool")
-	TokenPoolConfig    = ffm("TokenPool.config", "Input only field, with token connector specific configuration of the pool, such as an existing Ethereum address and block number to used to index the pool. See your chosen token connector documentation for details")
-	TokenPoolInfo      = ffm("TokenPool.info", "Token connector specific information about the pool. See your chosen token connector documentation for details")
-	TokenPoolTX        = ffm("TokenPool.tx", "Reference to the FireFly transaction used to create and broadcast this pool to the network")
+	TokenPoolID              = ffm("TokenPool.id", "The UUID of the token pool")
+	TokenPoolType            = ffm("TokenPool.type", "The type of token the pool contains, such as fungible/non-fungible")
+	TokenPoolNamespace       = ffm("TokenPool.namespace", "The namespace for the token pool")
+	TokenPoolName            = ffm("TokenPool.name", "The name of the token pool. Note the name is not validated against the description of the token on the blockchain")
+	TokenPoolStandard        = ffm("TokenPool.standard", "The ERC standard the token pool conforms to, as reported by the token connector")
+	TokenPoolLocator         = ffm("TokenPool.locator", "A unique identifier for the pool, as provided by the token connector")
+	TokenPoolKey             = ffm("TokenPool.key", "The signing key used to create the token pool. On input for token connectors that support on-chain deployment of new tokens (vs. only index existing ones) this determines the signing key used to create the token on-chain")
+	TokenPoolSymbol          = ffm("TokenPool.symbol", "The token symbol. If supplied on input for an existing on-chain token, this must match the on-chain information")
+	TokenPoolDecimals        = ffm("TokenPool.decimals", "Number of decimal places that this token has")
+	TokenPoolConnector       = ffm("TokenPool.connector", "The name of the token connector, as specified in the FireFly core configuration file that is responsible for the token pool. Required on input when multiple token connectors are configured")
+	TokenPoolMessage         = ffm("TokenPool.message", "The UUID of the broadcast message used to inform the network to index this pool")
+	TokenPoolState           = ffm("TokenPool.state", "The current state of the token pool")
+	TokenPoolCreated         = ffm("TokenPool.created", "The creation time of the pool")
+	TokenPoolConfig          = ffm("TokenPool.config", "Input only field, with token connector specific configuration of the pool, such as an existing Ethereum address and block number to used to index the pool. See your chosen token connector documentation for details")
+	TokenPoolInfo            = ffm("TokenPool.info", "Token connector specific information about the pool. See your chosen token connector documentation for details")
+	TokenPoolTX              = ffm("TokenPool.tx", "Reference to the FireFly transaction used to create and broadcast this pool to the network")
+	TokenPoolInterface       = ffm("TokenPool.interface", "A reference to an existing FFI, containing pre-registered type information for the token contract")
+	TokenPoolInterfaceFormat = ffm("TokenPool.interfaceFormat", "The interface encoding format supported by the connector for this token pool")
+	TokenPoolMethods         = ffm("TokenPool.methods", "The method definitions resolved by the token connector to be used by each token operation")
 
 	// TokenPoolInput field descriptions
 	TokenPoolInputIdempotencyKey = ffm("TokenPoolInput.idempotencyKey", "An optional identifier to allow idempotent submission of requests. Stored on the transaction uniquely within a namespace")
diff --git a/internal/database/sqlcommon/tokenpool_sql.go b/internal/database/sqlcommon/tokenpool_sql.go
index b06e32d1b..6778b80ad 100644
--- a/internal/database/sqlcommon/tokenpool_sql.go
+++ b/internal/database/sqlcommon/tokenpool_sql.go
@@ -1,4 +1,4 @@
-// Copyright © 2022 Kaleido, Inc.
+// Copyright © 2023 Kaleido, Inc.
 //
 // SPDX-License-Identifier: Apache-2.0
 //
@@ -47,11 +47,15 @@ var (
 		"tx_type",
 		"tx_id",
 		"info",
+		"interface",
+		"interface_format",
+		"methods",
 	}
 	tokenPoolFilterFieldMap = map[string]string{
-		"message": "message_id",
-		"tx.type": "tx_type",
-		"tx.id":   "tx_id",
+		"message":         "message_id",
+		"tx.type":         "tx_type",
+		"tx.id":           "tx_id",
+		"interfaceformat": "interface_format",
 	}
 )
 
@@ -88,6 +92,11 @@ func (s *SQLCommon) UpsertTokenPool(ctx context.Context, pool *core.TokenPool) (
 	}
 	rows.Close()
 
+	var interfaceID *fftypes.UUID
+	if pool.Interface != nil {
+		interfaceID = pool.Interface.ID
+	}
+
 	if existing {
 		if _, err = s.UpdateTx(ctx, tokenpoolTable, tx,
 			sq.Update(tokenpoolTable).
@@ -103,6 +112,9 @@ func (s *SQLCommon) UpsertTokenPool(ctx context.Context, pool *core.TokenPool) (
 				Set("tx_type", pool.TX.Type).
 				Set("tx_id", pool.TX.ID).
 				Set("info", pool.Info).
+				Set("interface", interfaceID).
+				Set("interface_format", pool.InterfaceFormat).
+				Set("methods", pool.Methods).
 				Where(sq.Eq{"id": pool.ID}),
 			func() {
 				s.callbacks.UUIDCollectionNSEvent(database.CollectionTokenPools, core.ChangeEventTypeUpdated, pool.Namespace, pool.ID)
@@ -131,6 +143,9 @@ func (s *SQLCommon) UpsertTokenPool(ctx context.Context, pool *core.TokenPool) (
 					pool.TX.Type,
 					pool.TX.ID,
 					pool.Info,
+					interfaceID,
+					pool.InterfaceFormat,
+					pool.Methods,
 				),
 			func() {
 				s.callbacks.UUIDCollectionNSEvent(database.CollectionTokenPools, core.ChangeEventTypeCreated, pool.Namespace, pool.ID)
@@ -145,6 +160,7 @@ func (s *SQLCommon) UpsertTokenPool(ctx context.Context, pool *core.TokenPool) (
 
 func (s *SQLCommon) tokenPoolResult(ctx context.Context, row *sql.Rows) (*core.TokenPool, error) {
 	pool := core.TokenPool{}
+	iface := fftypes.FFIReference{}
 	err := row.Scan(
 		&pool.ID,
 		&pool.Namespace,
@@ -161,7 +177,13 @@ func (s *SQLCommon) tokenPoolResult(ctx context.Context, row *sql.Rows) (*core.T
 		&pool.TX.Type,
 		&pool.TX.ID,
 		&pool.Info,
+		&iface.ID,
+		&pool.InterfaceFormat,
+		&pool.Methods,
 	)
+	if iface.ID != nil {
+		pool.Interface = &iface
+	}
 	if err != nil {
 		return nil, i18n.WrapError(ctx, err, coremsgs.MsgDBReadErr, tokenpoolTable)
 	}
diff --git a/internal/events/token_pool_created.go b/internal/events/token_pool_created.go
index 627f4491f..7d06b835c 100644
--- a/internal/events/token_pool_created.go
+++ b/internal/events/token_pool_created.go
@@ -1,4 +1,4 @@
-// Copyright © 2022 Kaleido, Inc.
+// Copyright © 2023 Kaleido, Inc.
 //
 // SPDX-License-Identifier: Apache-2.0
 //
@@ -34,6 +34,7 @@ func addPoolDetailsFromPlugin(ffPool *core.TokenPool, pluginPool *tokens.TokenPo
 	ffPool.Locator = pluginPool.PoolLocator
 	ffPool.Connector = pluginPool.Connector
 	ffPool.Standard = pluginPool.Standard
+	ffPool.InterfaceFormat = (core.TokenInterfaceFormat)(pluginPool.InterfaceFormat)
 	ffPool.Decimals = pluginPool.Decimals
 	if pluginPool.TX.ID != nil {
 		ffPool.TX = pluginPool.TX
@@ -180,9 +181,17 @@ func (em *eventManager) TokenPoolCreated(ti tokens.Plugin, pool *tokens.TokenPoo
 			em.aggregator.queueMessageRewind(msgIDforRewind)
 		}
 
-		// Announce the details of the new token pool
-		// Other nodes will pass these details to their own token connector for validation/activation of the pool
 		if announcePool != nil {
+			// If the pool is tied to a contract interface, resolve the methods to be used for later operations
+			if announcePool.Interface != nil && announcePool.Interface.ID != nil && announcePool.InterfaceFormat != "" {
+				log.L(em.ctx).Infof("Querying token connector interface, id=%s", announcePool.ID)
+				if err := em.assets.ResolvePoolMethods(em.ctx, announcePool); err != nil {
+					return err
+				}
+			}
+
+			// Announce the details of the new token pool
+			// Other nodes will pass these details to their own token connector for validation/activation of the pool
 			log.L(em.ctx).Infof("Announcing token pool, id=%s", announcePool.ID)
 			err = em.defsender.DefineTokenPool(em.ctx, &core.TokenPoolAnnouncement{
 				Pool: announcePool,
diff --git a/internal/orchestrator/orchestrator.go b/internal/orchestrator/orchestrator.go
index 3f6f5c164..7b463445d 100644
--- a/internal/orchestrator/orchestrator.go
+++ b/internal/orchestrator/orchestrator.go
@@ -473,13 +473,6 @@ func (or *orchestrator) initManagers(ctx context.Context) (err error) {
 		}
 	}
 
-	if or.assets == nil {
-		or.assets, err = assets.NewAssetManager(ctx, or.namespace.Name, or.config.KeyNormalization, or.database(), or.tokens(), or.identity, or.syncasync, or.broadcast, or.messaging, or.metrics, or.operations, or.txHelper)
-		if err != nil {
-			return err
-		}
-	}
-
 	if or.blockchain() != nil {
 		if or.contracts == nil {
 			or.contracts, err = contracts.NewContractManager(ctx, or.namespace.Name, or.database(), or.blockchain(), or.identity, or.operations, or.txHelper, or.syncasync)
@@ -489,6 +482,13 @@ func (or *orchestrator) initManagers(ctx context.Context) (err error) {
 		}
 	}
 
+	if or.assets == nil {
+		or.assets, err = assets.NewAssetManager(ctx, or.namespace.Name, or.config.KeyNormalization, or.database(), or.tokens(), or.identity, or.syncasync, or.broadcast, or.messaging, or.metrics, or.operations, or.contracts, or.txHelper)
+		if err != nil {
+			return err
+		}
+	}
+
 	if or.defsender == nil {
 		or.defsender, or.defhandler, err = definitions.NewDefinitionSender(ctx, or.namespace, or.config.Multiparty.Enabled, or.database(), or.blockchain(), or.dataexchange(), or.broadcast, or.identity, or.data, or.assets, or.contracts, or.config.TokenBroadcastNames)
 		if err != nil {
diff --git a/internal/tokens/fftokens/fftokens.go b/internal/tokens/fftokens/fftokens.go
index 33763d40c..adc1ebefb 100644
--- a/internal/tokens/fftokens/fftokens.go
+++ b/internal/tokens/fftokens/fftokens.go
@@ -1,4 +1,4 @@
-// Copyright © 2022 Kaleido, Inc.
+// Copyright © 2023 Kaleido, Inc.
 //
 // SPDX-License-Identifier: Apache-2.0
 //
@@ -27,6 +27,8 @@ import (
 	"github.com/hyperledger/firefly-common/pkg/i18n"
 	"github.com/hyperledger/firefly-common/pkg/log"
 	"github.com/hyperledger/firefly-common/pkg/wsclient"
+	"github.com/hyperledger/firefly-signer/pkg/abi"
+	"github.com/hyperledger/firefly-signer/pkg/ffi2abi"
 	"github.com/hyperledger/firefly/internal/coremsgs"
 	"github.com/hyperledger/firefly/pkg/blockchain"
 	"github.com/hyperledger/firefly/pkg/core"
@@ -151,6 +153,16 @@ type activatePool struct {
 	RequestID   string             `json:"requestId,omitempty"`
 }
 
+type tokenInterface struct {
+	Format  core.TokenInterfaceFormat `json:"format"`
+	Methods interface{}               `json:"methods,omitempty"`
+}
+
+type checkInterface struct {
+	tokenInterface
+	PoolLocator string `json:"poolLocator"`
+}
+
 type mintTokens struct {
 	PoolLocator string             `json:"poolLocator"`
 	TokenIndex  string             `json:"tokenIndex,omitempty"`
@@ -161,6 +173,7 @@ type mintTokens struct {
 	Data        string             `json:"data,omitempty"`
 	URI         string             `json:"uri,omitempty"`
 	Config      fftypes.JSONObject `json:"config"`
+	Interface   interface{}        `json:"interface,omitempty"`
 }
 
 type burnTokens struct {
@@ -172,6 +185,7 @@ type burnTokens struct {
 	Signer      string             `json:"signer"`
 	Data        string             `json:"data,omitempty"`
 	Config      fftypes.JSONObject `json:"config"`
+	Interface   interface{}        `json:"interface,omitempty"`
 }
 
 type transferTokens struct {
@@ -184,6 +198,7 @@ type transferTokens struct {
 	Signer      string             `json:"signer"`
 	Data        string             `json:"data,omitempty"`
 	Config      fftypes.JSONObject `json:"config"`
+	Interface   interface{}        `json:"interface,omitempty"`
 }
 
 type tokenApproval struct {
@@ -194,6 +209,7 @@ type tokenApproval struct {
 	RequestID   string             `json:"requestId,omitempty"`
 	Data        string             `json:"data,omitempty"`
 	Config      fftypes.JSONObject `json:"config"`
+	Interface   interface{}        `json:"interface,omitempty"`
 }
 
 type tokenError struct {
@@ -317,6 +333,7 @@ func (ft *FFTokens) handleTokenPoolCreate(ctx context.Context, data fftypes.JSON
 
 	// These fields are optional
 	standard := data.GetString("standard")
+	interfaceFormat := data.GetString("interfaceFormat")
 	symbol := data.GetString("symbol")
 	decimals := data.GetInt64("decimals")
 	info := data.GetObject("info")
@@ -344,12 +361,13 @@ func (ft *FFTokens) handleTokenPoolCreate(ctx context.Context, data fftypes.JSON
 			ID:   poolData.TX,
 			Type: txType,
 		},
-		Connector: ft.configuredName,
-		Standard:  standard,
-		Symbol:    symbol,
-		Decimals:  int(decimals),
-		Info:      info,
-		Event:     ft.buildBlockchainEvent(blockchainEvent),
+		Connector:       ft.configuredName,
+		Standard:        standard,
+		InterfaceFormat: interfaceFormat,
+		Symbol:          symbol,
+		Decimals:        int(decimals),
+		Info:            info,
+		Event:           ft.buildBlockchainEvent(blockchainEvent),
 	}
 
 	// If there's an error dispatching the event, we must return the error and shutdown
@@ -643,13 +661,68 @@ func (ft *FFTokens) ActivateTokenPool(ctx context.Context, nsOpID string, pool *
 	return false, nil
 }
 
-func (ft *FFTokens) MintTokens(ctx context.Context, nsOpID string, poolLocator string, mint *core.TokenTransfer) error {
+func (ft *FFTokens) prepareABI(ctx context.Context, methods []*fftypes.FFIMethod) ([]*abi.Entry, error) {
+	abiMethods := make([]*abi.Entry, len(methods))
+	for i, method := range methods {
+		abi, err := ffi2abi.ConvertFFIMethodToABI(ctx, method)
+		if err != nil {
+			return nil, err
+		}
+		abiMethods[i] = abi
+	}
+	return abiMethods, nil
+}
+
+func (ft *FFTokens) CheckInterface(ctx context.Context, pool *core.TokenPool, methods []*fftypes.FFIMethod) (*fftypes.JSONAny, error) {
+	body := checkInterface{
+		PoolLocator: pool.Locator,
+		tokenInterface: tokenInterface{
+			Format: pool.InterfaceFormat,
+		},
+	}
+
+	switch pool.InterfaceFormat {
+	case core.TokenInterfaceFormatFFI:
+		body.tokenInterface.Methods = methods
+	case core.TokenInterfaceFormatABI:
+		abi, err := ft.prepareABI(ctx, methods)
+		if err != nil {
+			return nil, err
+		}
+		body.tokenInterface.Methods = abi
+	default:
+		return nil, i18n.NewError(ctx, coremsgs.MsgUnknownInterfaceFormat, pool.InterfaceFormat)
+	}
+
+	var errRes tokenError
+	res, err := ft.client.R().SetContext(ctx).
+		SetBody(&body).
+		SetError(&errRes).
+		Post("/api/v1/checkinterface")
+	if err != nil || !res.IsSuccess() {
+		return nil, wrapError(ctx, &errRes, res, err)
+	}
+
+	var obj tokens.TokenPoolMethods
+	if err := json.Unmarshal(res.Body(), &obj); err != nil {
+		return nil, i18n.WrapError(ctx, err, i18n.MsgJSONObjectParseFailed, res.Body())
+	}
+	return fftypes.JSONAnyPtrBytes(res.Body()), nil
+}
+
+func (ft *FFTokens) MintTokens(ctx context.Context, nsOpID string, poolLocator string, mint *core.TokenTransfer, methods *fftypes.JSONAny) error {
 	data, _ := json.Marshal(tokenData{
 		TX:          mint.TX.ID,
 		TXType:      mint.TX.Type,
 		Message:     mint.Message,
 		MessageHash: mint.MessageHash,
 	})
+
+	var iface interface{}
+	if methods != nil {
+		iface = methods.JSONObject()["mint"]
+	}
+
 	var errRes tokenError
 	res, err := ft.client.R().SetContext(ctx).
 		SetBody(&mintTokens{
@@ -662,6 +735,7 @@ func (ft *FFTokens) MintTokens(ctx context.Context, nsOpID string, poolLocator s
 			Data:        string(data),
 			URI:         mint.URI,
 			Config:      mint.Config,
+			Interface:   iface,
 		}).
 		SetError(&errRes).
 		Post("/api/v1/mint")
@@ -671,13 +745,19 @@ func (ft *FFTokens) MintTokens(ctx context.Context, nsOpID string, poolLocator s
 	return nil
 }
 
-func (ft *FFTokens) BurnTokens(ctx context.Context, nsOpID string, poolLocator string, burn *core.TokenTransfer) error {
+func (ft *FFTokens) BurnTokens(ctx context.Context, nsOpID string, poolLocator string, burn *core.TokenTransfer, methods *fftypes.JSONAny) error {
 	data, _ := json.Marshal(tokenData{
 		TX:          burn.TX.ID,
 		TXType:      burn.TX.Type,
 		Message:     burn.Message,
 		MessageHash: burn.MessageHash,
 	})
+
+	var iface interface{}
+	if methods != nil {
+		iface = methods.JSONObject()["burn"]
+	}
+
 	var errRes tokenError
 	res, err := ft.client.R().SetContext(ctx).
 		SetBody(&burnTokens{
@@ -689,6 +769,7 @@ func (ft *FFTokens) BurnTokens(ctx context.Context, nsOpID string, poolLocator s
 			Signer:      burn.Key,
 			Data:        string(data),
 			Config:      burn.Config,
+			Interface:   iface,
 		}).
 		SetError(&errRes).
 		Post("/api/v1/burn")
@@ -698,13 +779,19 @@ func (ft *FFTokens) BurnTokens(ctx context.Context, nsOpID string, poolLocator s
 	return nil
 }
 
-func (ft *FFTokens) TransferTokens(ctx context.Context, nsOpID string, poolLocator string, transfer *core.TokenTransfer) error {
+func (ft *FFTokens) TransferTokens(ctx context.Context, nsOpID string, poolLocator string, transfer *core.TokenTransfer, methods *fftypes.JSONAny) error {
 	data, _ := json.Marshal(tokenData{
 		TX:          transfer.TX.ID,
 		TXType:      transfer.TX.Type,
 		Message:     transfer.Message,
 		MessageHash: transfer.MessageHash,
 	})
+
+	var iface interface{}
+	if methods != nil {
+		iface = methods.JSONObject()["transfer"]
+	}
+
 	var errRes tokenError
 	res, err := ft.client.R().SetContext(ctx).
 		SetBody(&transferTokens{
@@ -717,6 +804,7 @@ func (ft *FFTokens) TransferTokens(ctx context.Context, nsOpID string, poolLocat
 			Signer:      transfer.Key,
 			Data:        string(data),
 			Config:      transfer.Config,
+			Interface:   iface,
 		}).
 		SetError(&errRes).
 		Post("/api/v1/transfer")
@@ -726,13 +814,19 @@ func (ft *FFTokens) TransferTokens(ctx context.Context, nsOpID string, poolLocat
 	return nil
 }
 
-func (ft *FFTokens) TokensApproval(ctx context.Context, nsOpID string, poolLocator string, approval *core.TokenApproval) error {
+func (ft *FFTokens) TokensApproval(ctx context.Context, nsOpID string, poolLocator string, approval *core.TokenApproval, methods *fftypes.JSONAny) error {
 	data, _ := json.Marshal(tokenData{
 		TX:          approval.TX.ID,
 		TXType:      approval.TX.Type,
 		Message:     approval.Message,
 		MessageHash: approval.MessageHash,
 	})
+
+	var iface interface{}
+	if methods != nil {
+		iface = methods.JSONObject()["approval"]
+	}
+
 	var errRes tokenError
 	res, err := ft.client.R().SetContext(ctx).
 		SetBody(&tokenApproval{
@@ -743,6 +837,7 @@ func (ft *FFTokens) TokensApproval(ctx context.Context, nsOpID string, poolLocat
 			RequestID:   nsOpID,
 			Data:        string(data),
 			Config:      approval.Config,
+			Interface:   iface,
 		}).
 		SetError(&errRes).
 		Post("/api/v1/approval")
diff --git a/manifest.json b/manifest.json
index 6350410cc..b70fe1450 100644
--- a/manifest.json
+++ b/manifest.json
@@ -21,13 +21,13 @@
   },
   "tokens-erc1155": {
     "image": "ghcr.io/hyperledger/firefly-tokens-erc1155",
-    "tag": "v1.1.5",
-    "sha": "fb42ae2e1f6d4853d0cb19fee1e56175dcb3b34c2114fd890d1e0439b3e615fd"
+    "tag": "v1.2.0",
+    "sha": "9a7135b4d3352992832629ff644cf1a6526fef9de716711dbf00a7bf842184a6"
   },
   "tokens-erc20-erc721": {
     "image": "ghcr.io/hyperledger/firefly-tokens-erc20-erc721",
-    "tag": "v1.1.4",
-    "sha": "6b2ce04bfa73c38639a2cdaeec9cb01564c839b4dee5b837d73dfbd56282e19a"
+    "tag": "v1.2.0",
+    "sha": "88fbaee7d7b24682bea8fe0c00d9baa50c9a3bcc86aaed828a1f5e8ac205985a"
   },
   "signer": {
     "image": "ghcr.io/hyperledger/firefly-signer",
diff --git a/mocks/assetmocks/manager.go b/mocks/assetmocks/manager.go
index d1511398e..e6e6558f6 100644
--- a/mocks/assetmocks/manager.go
+++ b/mocks/assetmocks/manager.go
@@ -449,6 +449,20 @@ func (_m *Manager) PrepareOperation(ctx context.Context, op *core.Operation) (*c
 	return r0, r1
 }
 
+// ResolvePoolMethods provides a mock function with given fields: ctx, pool
+func (_m *Manager) ResolvePoolMethods(ctx context.Context, pool *core.TokenPool) error {
+	ret := _m.Called(ctx, pool)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, *core.TokenPool) error); ok {
+		r0 = rf(ctx, pool)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
 // RunOperation provides a mock function with given fields: ctx, op
 func (_m *Manager) RunOperation(ctx context.Context, op *core.PreparedOperation) (fftypes.JSONObject, bool, error) {
 	ret := _m.Called(ctx, op)
diff --git a/mocks/contractmocks/manager.go b/mocks/contractmocks/manager.go
index 1e86b25a4..89be0cde6 100644
--- a/mocks/contractmocks/manager.go
+++ b/mocks/contractmocks/manager.go
@@ -382,6 +382,52 @@ func (_m *Manager) GetFFIByIDWithChildren(ctx context.Context, id *fftypes.UUID)
 	return r0, r1
 }
 
+// GetFFIEvents provides a mock function with given fields: ctx, id
+func (_m *Manager) GetFFIEvents(ctx context.Context, id *fftypes.UUID) ([]*fftypes.FFIEvent, error) {
+	ret := _m.Called(ctx, id)
+
+	var r0 []*fftypes.FFIEvent
+	if rf, ok := ret.Get(0).(func(context.Context, *fftypes.UUID) []*fftypes.FFIEvent); ok {
+		r0 = rf(ctx, id)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).([]*fftypes.FFIEvent)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, *fftypes.UUID) error); ok {
+		r1 = rf(ctx, id)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// GetFFIMethods provides a mock function with given fields: ctx, id
+func (_m *Manager) GetFFIMethods(ctx context.Context, id *fftypes.UUID) ([]*fftypes.FFIMethod, error) {
+	ret := _m.Called(ctx, id)
+
+	var r0 []*fftypes.FFIMethod
+	if rf, ok := ret.Get(0).(func(context.Context, *fftypes.UUID) []*fftypes.FFIMethod); ok {
+		r0 = rf(ctx, id)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).([]*fftypes.FFIMethod)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, *fftypes.UUID) error); ok {
+		r1 = rf(ctx, id)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
 // GetFFIWithChildren provides a mock function with given fields: ctx, name, version
 func (_m *Manager) GetFFIWithChildren(ctx context.Context, name string, version string) (*fftypes.FFI, error) {
 	ret := _m.Called(ctx, name, version)
@@ -548,6 +594,20 @@ func (_m *Manager) ResolveFFI(ctx context.Context, ffi *fftypes.FFI) error {
 	return r0
 }
 
+// ResolveFFIReference provides a mock function with given fields: ctx, ref
+func (_m *Manager) ResolveFFIReference(ctx context.Context, ref *fftypes.FFIReference) error {
+	ret := _m.Called(ctx, ref)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, *fftypes.FFIReference) error); ok {
+		r0 = rf(ctx, ref)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
 // RunOperation provides a mock function with given fields: ctx, op
 func (_m *Manager) RunOperation(ctx context.Context, op *core.PreparedOperation) (fftypes.JSONObject, bool, error) {
 	ret := _m.Called(ctx, op)
diff --git a/mocks/tokenmocks/plugin.go b/mocks/tokenmocks/plugin.go
index 471e79842..1c5a0721d 100644
--- a/mocks/tokenmocks/plugin.go
+++ b/mocks/tokenmocks/plugin.go
@@ -9,6 +9,8 @@ import (
 
 	core "github.com/hyperledger/firefly/pkg/core"
 
+	fftypes "github.com/hyperledger/firefly-common/pkg/fftypes"
+
 	mock "github.com/stretchr/testify/mock"
 
 	tokens "github.com/hyperledger/firefly/pkg/tokens"
@@ -40,13 +42,13 @@ func (_m *Plugin) ActivateTokenPool(ctx context.Context, nsOpID string, pool *co
 	return r0, r1
 }
 
-// BurnTokens provides a mock function with given fields: ctx, nsOpID, poolLocator, burn
-func (_m *Plugin) BurnTokens(ctx context.Context, nsOpID string, poolLocator string, burn *core.TokenTransfer) error {
-	ret := _m.Called(ctx, nsOpID, poolLocator, burn)
+// BurnTokens provides a mock function with given fields: ctx, nsOpID, poolLocator, burn, methods
+func (_m *Plugin) BurnTokens(ctx context.Context, nsOpID string, poolLocator string, burn *core.TokenTransfer, methods *fftypes.JSONAny) error {
+	ret := _m.Called(ctx, nsOpID, poolLocator, burn, methods)
 
 	var r0 error
-	if rf, ok := ret.Get(0).(func(context.Context, string, string, *core.TokenTransfer) error); ok {
-		r0 = rf(ctx, nsOpID, poolLocator, burn)
+	if rf, ok := ret.Get(0).(func(context.Context, string, string, *core.TokenTransfer, *fftypes.JSONAny) error); ok {
+		r0 = rf(ctx, nsOpID, poolLocator, burn, methods)
 	} else {
 		r0 = ret.Error(0)
 	}
@@ -70,6 +72,29 @@ func (_m *Plugin) Capabilities() *tokens.Capabilities {
 	return r0
 }
 
+// CheckInterface provides a mock function with given fields: ctx, pool, methods
+func (_m *Plugin) CheckInterface(ctx context.Context, pool *core.TokenPool, methods []*fftypes.FFIMethod) (*fftypes.JSONAny, error) {
+	ret := _m.Called(ctx, pool, methods)
+
+	var r0 *fftypes.JSONAny
+	if rf, ok := ret.Get(0).(func(context.Context, *core.TokenPool, []*fftypes.FFIMethod) *fftypes.JSONAny); ok {
+		r0 = rf(ctx, pool, methods)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*fftypes.JSONAny)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, *core.TokenPool, []*fftypes.FFIMethod) error); ok {
+		r1 = rf(ctx, pool, methods)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
 // CreateTokenPool provides a mock function with given fields: ctx, nsOpID, pool
 func (_m *Plugin) CreateTokenPool(ctx context.Context, nsOpID string, pool *core.TokenPool) (bool, error) {
 	ret := _m.Called(ctx, nsOpID, pool)
@@ -110,13 +135,13 @@ func (_m *Plugin) InitConfig(_a0 config.KeySet) {
 	_m.Called(_a0)
 }
 
-// MintTokens provides a mock function with given fields: ctx, nsOpID, poolLocator, mint
-func (_m *Plugin) MintTokens(ctx context.Context, nsOpID string, poolLocator string, mint *core.TokenTransfer) error {
-	ret := _m.Called(ctx, nsOpID, poolLocator, mint)
+// MintTokens provides a mock function with given fields: ctx, nsOpID, poolLocator, mint, methods
+func (_m *Plugin) MintTokens(ctx context.Context, nsOpID string, poolLocator string, mint *core.TokenTransfer, methods *fftypes.JSONAny) error {
+	ret := _m.Called(ctx, nsOpID, poolLocator, mint, methods)
 
 	var r0 error
-	if rf, ok := ret.Get(0).(func(context.Context, string, string, *core.TokenTransfer) error); ok {
-		r0 = rf(ctx, nsOpID, poolLocator, mint)
+	if rf, ok := ret.Get(0).(func(context.Context, string, string, *core.TokenTransfer, *fftypes.JSONAny) error); ok {
+		r0 = rf(ctx, nsOpID, poolLocator, mint, methods)
 	} else {
 		r0 = ret.Error(0)
 	}
@@ -162,13 +187,13 @@ func (_m *Plugin) Start() error {
 	return r0
 }
 
-// TokensApproval provides a mock function with given fields: ctx, nsOpID, poolLocator, approval
-func (_m *Plugin) TokensApproval(ctx context.Context, nsOpID string, poolLocator string, approval *core.TokenApproval) error {
-	ret := _m.Called(ctx, nsOpID, poolLocator, approval)
+// TokensApproval provides a mock function with given fields: ctx, nsOpID, poolLocator, approval, methods
+func (_m *Plugin) TokensApproval(ctx context.Context, nsOpID string, poolLocator string, approval *core.TokenApproval, methods *fftypes.JSONAny) error {
+	ret := _m.Called(ctx, nsOpID, poolLocator, approval, methods)
 
 	var r0 error
-	if rf, ok := ret.Get(0).(func(context.Context, string, string, *core.TokenApproval) error); ok {
-		r0 = rf(ctx, nsOpID, poolLocator, approval)
+	if rf, ok := ret.Get(0).(func(context.Context, string, string, *core.TokenApproval, *fftypes.JSONAny) error); ok {
+		r0 = rf(ctx, nsOpID, poolLocator, approval, methods)
 	} else {
 		r0 = ret.Error(0)
 	}
@@ -176,13 +201,13 @@ func (_m *Plugin) TokensApproval(ctx context.Context, nsOpID string, poolLocator
 	return r0
 }
 
-// TransferTokens provides a mock function with given fields: ctx, nsOpID, poolLocator, transfer
-func (_m *Plugin) TransferTokens(ctx context.Context, nsOpID string, poolLocator string, transfer *core.TokenTransfer) error {
-	ret := _m.Called(ctx, nsOpID, poolLocator, transfer)
+// TransferTokens provides a mock function with given fields: ctx, nsOpID, poolLocator, transfer, methods
+func (_m *Plugin) TransferTokens(ctx context.Context, nsOpID string, poolLocator string, transfer *core.TokenTransfer, methods *fftypes.JSONAny) error {
+	ret := _m.Called(ctx, nsOpID, poolLocator, transfer, methods)
 
 	var r0 error
-	if rf, ok := ret.Get(0).(func(context.Context, string, string, *core.TokenTransfer) error); ok {
-		r0 = rf(ctx, nsOpID, poolLocator, transfer)
+	if rf, ok := ret.Get(0).(func(context.Context, string, string, *core.TokenTransfer, *fftypes.JSONAny) error); ok {
+		r0 = rf(ctx, nsOpID, poolLocator, transfer, methods)
 	} else {
 		r0 = ret.Error(0)
 	}
diff --git a/pkg/core/tokenpool.go b/pkg/core/tokenpool.go
index 69d31d681..af91eddeb 100644
--- a/pkg/core/tokenpool.go
+++ b/pkg/core/tokenpool.go
@@ -1,4 +1,4 @@
-// Copyright © 2022 Kaleido, Inc.
+// Copyright © 2023 Kaleido, Inc.
 //
 // SPDX-License-Identifier: Apache-2.0
 //
@@ -39,28 +39,38 @@ var (
 	TokenPoolStateConfirmed = fftypes.FFEnumValue("tokenpoolstate", "confirmed")
 )
 
+type TokenInterfaceFormat = fftypes.FFEnum
+
+var (
+	TokenInterfaceFormatABI = fftypes.FFEnumValue("tokeninterfaceformat", "abi")
+	TokenInterfaceFormatFFI = fftypes.FFEnumValue("tokeninterfaceformat", "ffi")
+)
+
 type TokenPoolInput struct {
 	TokenPool
 	IdempotencyKey IdempotencyKey `ffstruct:"TokenPoolInput" json:"idempotencyKey,omitempty" ffexcludeoutput:"true"`
 }
 
 type TokenPool struct {
-	ID        *fftypes.UUID      `ffstruct:"TokenPool" json:"id,omitempty" ffexcludeinput:"true"`
-	Type      TokenType          `ffstruct:"TokenPool" json:"type" ffenum:"tokentype"`
-	Namespace string             `ffstruct:"TokenPool" json:"namespace,omitempty" ffexcludeinput:"true"`
-	Name      string             `ffstruct:"TokenPool" json:"name,omitempty"`
-	Standard  string             `ffstruct:"TokenPool" json:"standard,omitempty" ffexcludeinput:"true"`
-	Locator   string             `ffstruct:"TokenPool" json:"locator,omitempty" ffexcludeinput:"true"`
-	Key       string             `ffstruct:"TokenPool" json:"key,omitempty"`
-	Symbol    string             `ffstruct:"TokenPool" json:"symbol,omitempty"`
-	Decimals  int                `ffstruct:"TokenPool" json:"decimals,omitempty" ffexcludeinput:"true"`
-	Connector string             `ffstruct:"TokenPool" json:"connector,omitempty"`
-	Message   *fftypes.UUID      `ffstruct:"TokenPool" json:"message,omitempty" ffexcludeinput:"true"`
-	State     TokenPoolState     `ffstruct:"TokenPool" json:"state,omitempty" ffenum:"tokenpoolstate" ffexcludeinput:"true"`
-	Created   *fftypes.FFTime    `ffstruct:"TokenPool" json:"created,omitempty" ffexcludeinput:"true"`
-	Config    fftypes.JSONObject `ffstruct:"TokenPool" json:"config,omitempty" ffexcludeoutput:"true"` // for REST calls only (not stored)
-	Info      fftypes.JSONObject `ffstruct:"TokenPool" json:"info,omitempty" ffexcludeinput:"true"`
-	TX        TransactionRef     `ffstruct:"TokenPool" json:"tx,omitempty" ffexcludeinput:"true"`
+	ID              *fftypes.UUID         `ffstruct:"TokenPool" json:"id,omitempty" ffexcludeinput:"true"`
+	Type            TokenType             `ffstruct:"TokenPool" json:"type" ffenum:"tokentype"`
+	Namespace       string                `ffstruct:"TokenPool" json:"namespace,omitempty" ffexcludeinput:"true"`
+	Name            string                `ffstruct:"TokenPool" json:"name,omitempty"`
+	Standard        string                `ffstruct:"TokenPool" json:"standard,omitempty" ffexcludeinput:"true"`
+	Locator         string                `ffstruct:"TokenPool" json:"locator,omitempty" ffexcludeinput:"true"`
+	Key             string                `ffstruct:"TokenPool" json:"key,omitempty"`
+	Symbol          string                `ffstruct:"TokenPool" json:"symbol,omitempty"`
+	Decimals        int                   `ffstruct:"TokenPool" json:"decimals,omitempty" ffexcludeinput:"true"`
+	Connector       string                `ffstruct:"TokenPool" json:"connector,omitempty"`
+	Message         *fftypes.UUID         `ffstruct:"TokenPool" json:"message,omitempty" ffexcludeinput:"true"`
+	State           TokenPoolState        `ffstruct:"TokenPool" json:"state,omitempty" ffenum:"tokenpoolstate" ffexcludeinput:"true"`
+	Created         *fftypes.FFTime       `ffstruct:"TokenPool" json:"created,omitempty" ffexcludeinput:"true"`
+	Config          fftypes.JSONObject    `ffstruct:"TokenPool" json:"config,omitempty" ffexcludeoutput:"true"` // for REST calls only (not stored)
+	Info            fftypes.JSONObject    `ffstruct:"TokenPool" json:"info,omitempty" ffexcludeinput:"true"`
+	TX              TransactionRef        `ffstruct:"TokenPool" json:"tx,omitempty" ffexcludeinput:"true"`
+	Interface       *fftypes.FFIReference `ffstruct:"TokenPool" json:"interface,omitempty"`
+	InterfaceFormat TokenInterfaceFormat  `ffstruct:"TokenPool" json:"interfaceFormat,omitempty" ffenum:"tokeninterfaceformat" ffexcludeinput:"true"`
+	Methods         *fftypes.JSONAny      `ffstruct:"TokenPool" json:"methods,omitempty" ffexcludeinput:"true"`
 }
 
 type TokenPoolAnnouncement struct {
diff --git a/pkg/database/plugin.go b/pkg/database/plugin.go
index b0dd534b8..a24ca27b3 100644
--- a/pkg/database/plugin.go
+++ b/pkg/database/plugin.go
@@ -865,19 +865,21 @@ var BlobQueryFactory = &ffapi.QueryFields{
 
 // TokenPoolQueryFactory filter fields for token pools
 var TokenPoolQueryFactory = &ffapi.QueryFields{
-	"id":        &ffapi.UUIDField{},
-	"type":      &ffapi.StringField{},
-	"name":      &ffapi.StringField{},
-	"standard":  &ffapi.StringField{},
-	"locator":   &ffapi.StringField{},
-	"symbol":    &ffapi.StringField{},
-	"decimals":  &ffapi.Int64Field{},
-	"message":   &ffapi.UUIDField{},
-	"state":     &ffapi.StringField{},
-	"created":   &ffapi.TimeField{},
-	"connector": &ffapi.StringField{},
-	"tx.type":   &ffapi.StringField{},
-	"tx.id":     &ffapi.UUIDField{},
+	"id":              &ffapi.UUIDField{},
+	"type":            &ffapi.StringField{},
+	"name":            &ffapi.StringField{},
+	"standard":        &ffapi.StringField{},
+	"locator":         &ffapi.StringField{},
+	"symbol":          &ffapi.StringField{},
+	"decimals":        &ffapi.Int64Field{},
+	"message":         &ffapi.UUIDField{},
+	"state":           &ffapi.StringField{},
+	"created":         &ffapi.TimeField{},
+	"connector":       &ffapi.StringField{},
+	"tx.type":         &ffapi.StringField{},
+	"tx.id":           &ffapi.UUIDField{},
+	"interface":       &ffapi.UUIDField{},
+	"interfaceformat": &ffapi.StringField{},
 }
 
 // TokenBalanceQueryFactory filter fields for token balances
diff --git a/pkg/tokens/plugin.go b/pkg/tokens/plugin.go
index e5d7e9ae5..941a56fcd 100644
--- a/pkg/tokens/plugin.go
+++ b/pkg/tokens/plugin.go
@@ -1,4 +1,4 @@
-// Copyright © 2022 Kaleido, Inc.
+// Copyright © 2023 Kaleido, Inc.
 //
 // SPDX-License-Identifier: Apache-2.0
 //
@@ -43,7 +43,7 @@ type Plugin interface {
 	// If namespace is set, plugin will attempt to deliver only events for that namespace
 	SetOperationHandler(namespace string, handler core.OperationCallbacks)
 
-	// Blockchain interface must not deliver any events until start is called
+	// Token interface must not deliver any events until start is called
 	Start() error
 
 	// Capabilities returns capabilities - not called until after Init
@@ -55,17 +55,20 @@ type Plugin interface {
 	// ActivateTokenPool activates a pool in order to begin receiving events
 	ActivateTokenPool(ctx context.Context, nsOpID string, pool *core.TokenPool) (complete bool, err error)
 
+	// CheckInterface checks which methods of a contract interface are supported by this connector
+	CheckInterface(ctx context.Context, pool *core.TokenPool, methods []*fftypes.FFIMethod) (*fftypes.JSONAny, error)
+
 	// MintTokens mints new tokens in a pool and adds them to the recipient's account
-	MintTokens(ctx context.Context, nsOpID string, poolLocator string, mint *core.TokenTransfer) error
+	MintTokens(ctx context.Context, nsOpID string, poolLocator string, mint *core.TokenTransfer, methods *fftypes.JSONAny) error
 
 	// BurnTokens burns tokens from an account
-	BurnTokens(ctx context.Context, nsOpID string, poolLocator string, burn *core.TokenTransfer) error
+	BurnTokens(ctx context.Context, nsOpID string, poolLocator string, burn *core.TokenTransfer, methods *fftypes.JSONAny) error
 
 	// TransferTokens transfers tokens within a pool from one account to another
-	TransferTokens(ctx context.Context, nsOpID string, poolLocator string, transfer *core.TokenTransfer) error
+	TransferTokens(ctx context.Context, nsOpID string, poolLocator string, transfer *core.TokenTransfer, methods *fftypes.JSONAny) error
 
 	// TokenApproval approves an operator to transfer tokens on the owner's behalf
-	TokensApproval(ctx context.Context, nsOpID string, poolLocator string, approval *core.TokenApproval) error
+	TokensApproval(ctx context.Context, nsOpID string, poolLocator string, approval *core.TokenApproval, methods *fftypes.JSONAny) error
 }
 
 // Callbacks is the interface provided to the tokens plugin, to allow it to pass events back to firefly.
@@ -112,7 +115,10 @@ type TokenPool struct {
 	// Standard is the well-defined token standard that this pool conforms to (optional)
 	Standard string
 
-	// Decimals is the number of decimal places that this token has
+	// InterfaceFormat is the interface encoding format understood by this connector (optional)
+	InterfaceFormat string
+
+	// Decimals is the number of decimal places that this token has (optional)
 	Decimals int
 
 	// Symbol is the short token symbol, if the connector uses one (optional)
@@ -125,6 +131,14 @@ type TokenPool struct {
 	Event *blockchain.Event
 }
 
+// TokenPoolMethods is a mapped set of interface methods to be used for various token operations
+type TokenPoolMethods struct {
+	Approval *fftypes.JSONAny `json:"approval"`
+	Burn     *fftypes.JSONAny `json:"burn"`
+	Mint     *fftypes.JSONAny `json:"mint"`
+	Transfer *fftypes.JSONAny `json:"transfer"`
+}
+
 type TokenTransfer struct {
 	// Although not every field will be filled in, embed core.TokenTransfer to avoid duplicating lots of fields
 	// Notable fields NOT expected to be populated by plugins: Namespace, LocalID, Pool


Test patch:
diff --git a/internal/assets/manager_test.go b/internal/assets/manager_test.go
index 36ffef150..19252e56f 100644
--- a/internal/assets/manager_test.go
+++ b/internal/assets/manager_test.go
@@ -6,7 +6,7 @@
 // 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
+//	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,
@@ -25,6 +25,7 @@ import (
 	"github.com/hyperledger/firefly/internal/txcommon"
 	"github.com/hyperledger/firefly/mocks/broadcastmocks"
 	"github.com/hyperledger/firefly/mocks/cachemocks"
+	"github.com/hyperledger/firefly/mocks/contractmocks"
 	"github.com/hyperledger/firefly/mocks/databasemocks"
 	"github.com/hyperledger/firefly/mocks/datamocks"
 	"github.com/hyperledger/firefly/mocks/identitymanagermocks"
@@ -63,13 +64,14 @@ func newTestAssetsCommon(t *testing.T, metrics bool) (*assetManager, func()) {
 	ctx := context.Background()
 	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
 	mom := &operationmocks.Manager{}
+	mcm := &contractmocks.Manager{}
 	txHelper, _ := txcommon.NewTransactionHelper(ctx, "ns1", mdi, mdm, cmi)
 	mm.On("IsMetricsEnabled").Return(metrics)
 	mm.On("TransferSubmitted", mock.Anything)
 	mom.On("RegisterHandler", mock.Anything, mock.Anything, mock.Anything)
 	mti.On("Name").Return("ut").Maybe()
 	ctx, cancel := context.WithCancel(ctx)
-	a, err := NewAssetManager(ctx, "ns1", "blockchain_plugin", mdi, map[string]tokens.Plugin{"magic-tokens": mti}, mim, msa, mbm, mpm, mm, mom, txHelper)
+	a, err := NewAssetManager(ctx, "ns1", "blockchain_plugin", mdi, map[string]tokens.Plugin{"magic-tokens": mti}, mim, msa, mbm, mpm, mm, mom, mcm, txHelper)
 	rag := mdi.On("RunAsGroup", mock.Anything, mock.Anything).Maybe()
 	rag.RunFn = func(a mock.Arguments) {
 		rag.ReturnArguments = mock.Arguments{a[1].(func(context.Context) error)(a[0].(context.Context))}
@@ -81,7 +83,7 @@ func newTestAssetsCommon(t *testing.T, metrics bool) (*assetManager, func()) {
 }
 
 func TestInitFail(t *testing.T) {
-	_, err := NewAssetManager(context.Background(), "", "", nil, nil, nil, nil, nil, nil, nil, nil, nil)
+	_, err := NewAssetManager(context.Background(), "", "", nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
 	assert.Regexp(t, "FF10128", err)
 }
 
diff --git a/internal/assets/operations_test.go b/internal/assets/operations_test.go
index 2d132580d..c9f89d4a2 100644
--- a/internal/assets/operations_test.go
+++ b/internal/assets/operations_test.go
@@ -6,7 +6,7 @@
 // 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
+//	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,
@@ -116,7 +116,7 @@ func TestPrepareAndRunTransfer(t *testing.T) {
 
 	mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin)
 	mdi := am.database.(*databasemocks.Plugin)
-	mti.On("TransferTokens", context.Background(), "ns1:"+op.ID.String(), "F1", transfer).Return(nil)
+	mti.On("TransferTokens", context.Background(), "ns1:"+op.ID.String(), "F1", transfer, (*fftypes.JSONAny)(nil)).Return(nil)
 	mdi.On("GetTokenPoolByID", context.Background(), "ns1", pool.ID).Return(pool, nil)
 
 	po, err := am.PrepareOperation(context.Background(), op)
@@ -155,7 +155,7 @@ func TestPrepareAndRunApproval(t *testing.T) {
 
 	mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin)
 	mdi := am.database.(*databasemocks.Plugin)
-	mti.On("TokensApproval", context.Background(), "ns1:"+op.ID.String(), "F1", approval).Return(nil)
+	mti.On("TokensApproval", context.Background(), "ns1:"+op.ID.String(), "F1", approval, (*fftypes.JSONAny)(nil)).Return(nil)
 	mdi.On("GetTokenPoolByID", context.Background(), "ns1", pool.ID).Return(pool, nil)
 
 	po, err := am.PrepareOperation(context.Background(), op)
@@ -471,7 +471,35 @@ func TestRunOperationTransferMint(t *testing.T) {
 	}
 
 	mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin)
-	mti.On("MintTokens", context.Background(), "ns1:"+op.ID.String(), "F1", transfer).Return(nil)
+	mti.On("MintTokens", context.Background(), "ns1:"+op.ID.String(), "F1", transfer, (*fftypes.JSONAny)(nil)).Return(nil)
+
+	_, complete, err := am.RunOperation(context.Background(), opTransfer(op, pool, transfer))
+
+	assert.False(t, complete)
+	assert.NoError(t, err)
+
+	mti.AssertExpectations(t)
+}
+
+func TestRunOperationTransferMintWithInterface(t *testing.T) {
+	am, cancel := newTestAssets(t)
+	defer cancel()
+
+	op := &core.Operation{
+		ID:        fftypes.NewUUID(),
+		Namespace: "ns1",
+	}
+	pool := &core.TokenPool{
+		Connector: "magic-tokens",
+		Locator:   "F1",
+		Methods:   fftypes.JSONAnyPtr(`{"mint": "test_interface"}`),
+	}
+	transfer := &core.TokenTransfer{
+		Type: core.TokenTransferTypeMint,
+	}
+
+	mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin)
+	mti.On("MintTokens", context.Background(), "ns1:"+op.ID.String(), "F1", transfer, pool.Methods).Return(nil)
 
 	_, complete, err := am.RunOperation(context.Background(), opTransfer(op, pool, transfer))
 
@@ -498,7 +526,7 @@ func TestRunOperationTransferBurn(t *testing.T) {
 	}
 
 	mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin)
-	mti.On("BurnTokens", context.Background(), "ns1:"+op.ID.String(), "F1", transfer).Return(nil)
+	mti.On("BurnTokens", context.Background(), "ns1:"+op.ID.String(), "F1", transfer, (*fftypes.JSONAny)(nil)).Return(nil)
 
 	_, complete, err := am.RunOperation(context.Background(), opTransfer(op, pool, transfer))
 
@@ -525,7 +553,7 @@ func TestRunOperationTransfer(t *testing.T) {
 	}
 
 	mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin)
-	mti.On("TransferTokens", context.Background(), "ns1:"+op.ID.String(), "F1", transfer).Return(nil)
+	mti.On("TransferTokens", context.Background(), "ns1:"+op.ID.String(), "F1", transfer, (*fftypes.JSONAny)(nil)).Return(nil)
 
 	_, complete, err := am.RunOperation(context.Background(), opTransfer(op, pool, transfer))
 
diff --git a/internal/assets/token_pool_test.go b/internal/assets/token_pool_test.go
index 18ff1c7a4..7c2ace87e 100644
--- a/internal/assets/token_pool_test.go
+++ b/internal/assets/token_pool_test.go
@@ -22,10 +22,12 @@ import (
 	"github.com/hyperledger/firefly-common/pkg/fftypes"
 	"github.com/hyperledger/firefly/internal/identity"
 	"github.com/hyperledger/firefly/internal/syncasync"
+	"github.com/hyperledger/firefly/mocks/contractmocks"
 	"github.com/hyperledger/firefly/mocks/databasemocks"
 	"github.com/hyperledger/firefly/mocks/identitymanagermocks"
 	"github.com/hyperledger/firefly/mocks/operationmocks"
 	"github.com/hyperledger/firefly/mocks/syncasyncmocks"
+	"github.com/hyperledger/firefly/mocks/tokenmocks"
 	"github.com/hyperledger/firefly/mocks/txcommonmocks"
 	"github.com/hyperledger/firefly/pkg/core"
 	"github.com/hyperledger/firefly/pkg/database"
@@ -314,6 +316,33 @@ func TestCreateTokenPoolOpInsertFail(t *testing.T) {
 	mom.AssertExpectations(t)
 }
 
+func TestCreateTokenPoolWithInterfaceFail(t *testing.T) {
+	am, cancel := newTestAssets(t)
+	defer cancel()
+
+	pool := &core.TokenPoolInput{
+		TokenPool: core.TokenPool{
+			Connector: "magic-tokens",
+			Name:      "testpool",
+			Interface: &fftypes.FFIReference{
+				ID: fftypes.NewUUID(),
+			},
+		},
+		IdempotencyKey: "idem1",
+	}
+
+	mdi := am.database.(*databasemocks.Plugin)
+	mcm := am.contracts.(*contractmocks.Manager)
+	mdi.On("GetTokenPool", context.Background(), "ns1", "testpool").Return(nil, nil)
+	mcm.On("ResolveFFIReference", context.Background(), pool.Interface).Return(fmt.Errorf("pop"))
+
+	_, err := am.CreateTokenPool(context.Background(), pool, false)
+	assert.EqualError(t, err, "pop")
+
+	mdi.AssertExpectations(t)
+	mcm.AssertExpectations(t)
+}
+
 func TestCreateTokenPoolSyncSuccess(t *testing.T) {
 	am, cancel := newTestAssets(t)
 	defer cancel()
@@ -725,3 +754,30 @@ func TestGetTokenPools(t *testing.T) {
 
 	mdi.AssertExpectations(t)
 }
+
+func TestResolvePoolMethods(t *testing.T) {
+	am, cancel := newTestAssets(t)
+	defer cancel()
+
+	pool := &core.TokenPool{
+		Connector: "magic-tokens",
+		Interface: &fftypes.FFIReference{
+			ID: fftypes.NewUUID(),
+		},
+	}
+	methods := []*fftypes.FFIMethod{{Name: "method1"}}
+	resolved := fftypes.JSONAnyPtr(`"resolved"`)
+
+	mcm := am.contracts.(*contractmocks.Manager)
+	mcm.On("GetFFIMethods", context.Background(), pool.Interface.ID).Return(methods, nil)
+
+	mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin)
+	mti.On("CheckInterface", context.Background(), pool, methods).Return(resolved, nil)
+
+	err := am.ResolvePoolMethods(context.Background(), pool)
+	assert.NoError(t, err)
+	assert.Equal(t, resolved, pool.Methods)
+
+	mcm.AssertExpectations(t)
+	mti.AssertExpectations(t)
+}
diff --git a/internal/database/sqlcommon/tokenpool_sql_test.go b/internal/database/sqlcommon/tokenpool_sql_test.go
index 2b0813256..816b348f3 100644
--- a/internal/database/sqlcommon/tokenpool_sql_test.go
+++ b/internal/database/sqlcommon/tokenpool_sql_test.go
@@ -1,4 +1,4 @@
-// Copyright © 2022 Kaleido, Inc.
+// Copyright © 2023 Kaleido, Inc.
 //
 // SPDX-License-Identifier: Apache-2.0
 //
@@ -57,6 +57,10 @@ func TestTokenPoolE2EWithDB(t *testing.T) {
 		Info: fftypes.JSONObject{
 			"pool": "info",
 		},
+		Interface: &fftypes.FFIReference{
+			ID: fftypes.NewUUID(),
+		},
+		InterfaceFormat: "abi",
 	}
 
 	s.callbacks.On("UUIDCollectionNSEvent", database.CollectionTokenPools, core.ChangeEventTypeCreated, "ns1", poolID, mock.Anything).
diff --git a/internal/events/token_pool_created_test.go b/internal/events/token_pool_created_test.go
index 45e3d74a0..5723d60a2 100644
--- a/internal/events/token_pool_created_test.go
+++ b/internal/events/token_pool_created_test.go
@@ -370,6 +370,57 @@ func TestTokenPoolCreatedAnnounce(t *testing.T) {
 	mti.AssertExpectations(t)
 }
 
+func TestTokenPoolCreatedAnnounceBadInterface(t *testing.T) {
+	em := newTestEventManager(t)
+	defer em.cleanup(t)
+	mti := &tokenmocks.Plugin{}
+
+	poolID := fftypes.NewUUID()
+	txID := fftypes.NewUUID()
+	interfaceID := fftypes.NewUUID()
+	operations := []*core.Operation{
+		{
+			ID: fftypes.NewUUID(),
+			Input: fftypes.JSONObject{
+				"id":        poolID.String(),
+				"namespace": "ns1",
+				"name":      "my-pool",
+				"interface": fftypes.JSONObject{
+					"id": interfaceID.String(),
+				},
+			},
+		},
+	}
+	info := fftypes.JSONObject{"some": "info"}
+	pool := &tokens.TokenPool{
+		Type:        core.TokenTypeFungible,
+		PoolLocator: "123",
+		TX: core.TransactionRef{
+			ID:   txID,
+			Type: core.TransactionTypeTokenPool,
+		},
+		Connector: "erc1155",
+		Event: &blockchain.Event{
+			BlockchainTXID: "0xffffeeee",
+			ProtocolID:     "tx1",
+			Info:           info,
+		},
+		InterfaceFormat: "abi",
+	}
+
+	em.mdi.On("GetTokenPoolByLocator", em.ctx, "ns1", "erc1155", "123").Return(nil, nil).Times(2)
+	em.mdi.On("GetOperations", em.ctx, "ns1", mock.Anything).Return(nil, nil, fmt.Errorf("pop")).Once()
+	em.mdi.On("GetOperations", em.ctx, "ns1", mock.Anything).Return(operations, nil, nil).Once()
+	em.mam.On("ResolvePoolMethods", em.ctx, mock.MatchedBy(func(pool *core.TokenPool) bool {
+		return pool.Locator == "123" && pool.Name == "my-pool"
+	})).Return(fmt.Errorf("pop"))
+
+	err := em.TokenPoolCreated(mti, pool)
+	assert.EqualError(t, err, "pop")
+
+	mti.AssertExpectations(t)
+}
+
 func TestTokenPoolCreatedAnnounceBadOpInputID(t *testing.T) {
 	em := newTestEventManager(t)
 	defer em.cleanup(t)
diff --git a/internal/tokens/fftokens/fftokens_test.go b/internal/tokens/fftokens/fftokens_test.go
index be70801ed..9736cfe55 100644
--- a/internal/tokens/fftokens/fftokens_test.go
+++ b/internal/tokens/fftokens/fftokens_test.go
@@ -572,7 +572,65 @@ func TestMintTokens(t *testing.T) {
 			return res, nil
 		})
 
-	err := h.MintTokens(context.Background(), nsOpID, "123", mint)
+	err := h.MintTokens(context.Background(), nsOpID, "123", mint, nil)
+	assert.NoError(t, err)
+}
+
+func TestMintTokensWithInterface(t *testing.T) {
+	h, _, _, httpURL, done := newTestFFTokens(t)
+	defer done()
+
+	mint := &core.TokenTransfer{
+		LocalID: fftypes.NewUUID(),
+		To:      "user1",
+		Key:     "0x123",
+		Amount:  *fftypes.NewFFBigInt(10),
+		TX: core.TransactionRef{
+			ID:   fftypes.NewUUID(),
+			Type: core.TransactionTypeTokenTransfer,
+		},
+		URI: "FLAPFLIP",
+		Config: fftypes.JSONObject{
+			"foo": "bar",
+		},
+	}
+	methods := fftypes.JSONAnyPtr(`{"mint": "test_interface"}`)
+	opID := fftypes.NewUUID()
+	nsOpID := "ns1:" + opID.String()
+
+	httpmock.RegisterResponder("POST", fmt.Sprintf("%s/api/v1/mint", httpURL),
+		func(req *http.Request) (*http.Response, error) {
+			body := make(fftypes.JSONObject)
+			err := json.NewDecoder(req.Body).Decode(&body)
+			assert.NoError(t, err)
+			assert.Equal(t, fftypes.JSONObject{
+				"poolLocator": "123",
+				"to":          "user1",
+				"amount":      "10",
+				"signer":      "0x123",
+				"config": map[string]interface{}{
+					"foo": "bar",
+				},
+				"requestId": "ns1:" + opID.String(),
+				"data": fftypes.JSONObject{
+					"tx":     mint.TX.ID.String(),
+					"txtype": core.TransactionTypeTokenTransfer.String(),
+				}.String(),
+				"uri":       "FLAPFLIP",
+				"interface": "test_interface",
+			}, body)
+
+			res := &http.Response{
+				Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"id":"1"}`))),
+				Header: http.Header{
+					"Content-Type": []string{"application/json"},
+				},
+				StatusCode: 202,
+			}
+			return res, nil
+		})
+
+	err := h.MintTokens(context.Background(), nsOpID, "123", mint, methods)
 	assert.NoError(t, err)
 }
 
@@ -626,7 +684,7 @@ func TestTokenApproval(t *testing.T) {
 			return res, nil
 		})
 
-	err := h.TokensApproval(context.Background(), nsOpID, "123", approval)
+	err := h.TokensApproval(context.Background(), nsOpID, "123", approval, nil)
 	assert.NoError(t, err)
 }
 
@@ -635,12 +693,13 @@ func TestTokenApprovalError(t *testing.T) {
 	defer done()
 
 	approval := &core.TokenApproval{}
+	methods := fftypes.JSONAnyPtr(`{}`)
 
 	httpmock.RegisterResponder("POST", fmt.Sprintf("%s/api/v1/approval", httpURL),
 		httpmock.NewJsonResponderOrPanic(500, fftypes.JSONObject{}))
 
 	nsOpID := "ns1:" + fftypes.NewUUID().String()
-	err := h.TokensApproval(context.Background(), nsOpID, "F1", approval)
+	err := h.TokensApproval(context.Background(), nsOpID, "F1", approval, methods)
 	assert.Regexp(t, "FF10274", err)
 }
 
@@ -654,7 +713,7 @@ func TestMintTokensError(t *testing.T) {
 		httpmock.NewJsonResponderOrPanic(500, fftypes.JSONObject{}))
 
 	nsOpID := "ns1:" + fftypes.NewUUID().String()
-	err := h.MintTokens(context.Background(), nsOpID, "F1", mint)
+	err := h.MintTokens(context.Background(), nsOpID, "F1", mint, nil)
 	assert.Regexp(t, "FF10274", err)
 }
 
@@ -710,7 +769,7 @@ func TestBurnTokens(t *testing.T) {
 			return res, nil
 		})
 
-	err := h.BurnTokens(context.Background(), nsOpID, "123", burn)
+	err := h.BurnTokens(context.Background(), nsOpID, "123", burn, nil)
 	assert.NoError(t, err)
 }
 
@@ -719,12 +778,13 @@ func TestBurnTokensError(t *testing.T) {
 	defer done()
 
 	burn := &core.TokenTransfer{}
+	methods := fftypes.JSONAnyPtr(`{}`)
 
 	httpmock.RegisterResponder("POST", fmt.Sprintf("%s/api/v1/burn", httpURL),
 		httpmock.NewJsonResponderOrPanic(500, fftypes.JSONObject{}))
 
 	nsOpID := "ns1:" + fftypes.NewUUID().String()
-	err := h.BurnTokens(context.Background(), nsOpID, "F1", burn)
+	err := h.BurnTokens(context.Background(), nsOpID, "F1", burn, methods)
 	assert.Regexp(t, "FF10274", err)
 }
 
@@ -782,7 +842,7 @@ func TestTransferTokens(t *testing.T) {
 			return res, nil
 		})
 
-	err := h.TransferTokens(context.Background(), nsOpID, "123", transfer)
+	err := h.TransferTokens(context.Background(), nsOpID, "123", transfer, nil)
 	assert.NoError(t, err)
 }
 
@@ -791,12 +851,13 @@ func TestTransferTokensError(t *testing.T) {
 	defer done()
 
 	transfer := &core.TokenTransfer{}
+	methods := fftypes.JSONAnyPtr(`{}`)
 
 	httpmock.RegisterResponder("POST", fmt.Sprintf("%s/api/v1/transfer", httpURL),
 		httpmock.NewJsonResponderOrPanic(500, fftypes.JSONObject{}))
 
 	nsOpID := "ns1:" + fftypes.NewUUID().String()
-	err := h.TransferTokens(context.Background(), nsOpID, "F1", transfer)
+	err := h.TransferTokens(context.Background(), nsOpID, "F1", transfer, methods)
 	assert.Regexp(t, "FF10274", err)
 }
 
@@ -1391,3 +1452,179 @@ func TestCallbacksWrongNamespace(t *testing.T) {
 	h.callbacks.TokensTransferred(context.Background(), "ns1", nil)
 	h.callbacks.TokensApproved(context.Background(), "ns1", nil)
 }
+
+func TestCheckInterfaceBadFormat(t *testing.T) {
+	h, _, _, _, done := newTestFFTokens(t)
+	defer done()
+
+	pool := &core.TokenPool{
+		InterfaceFormat: "bad",
+	}
+
+	_, err := h.CheckInterface(context.Background(), pool, nil)
+	assert.Regexp(t, "FF10435.*bad", err)
+}
+
+func TestCheckInterfaceABI(t *testing.T) {
+	h, _, _, httpURL, done := newTestFFTokens(t)
+	defer done()
+
+	pool := &core.TokenPool{
+		InterfaceFormat: "abi",
+		Locator:         "N1",
+	}
+	methods := []*fftypes.FFIMethod{
+		{Name: "method1"},
+	}
+
+	httpmock.RegisterResponder("POST", fmt.Sprintf("%s/api/v1/checkinterface", httpURL),
+		func(req *http.Request) (*http.Response, error) {
+			body := make(fftypes.JSONObject)
+			err := json.NewDecoder(req.Body).Decode(&body)
+			assert.NoError(t, err)
+			assert.Equal(t, fftypes.JSONObject{
+				"format":      "abi",
+				"poolLocator": "N1",
+				"methods": []interface{}{
+					map[string]interface{}{
+						"name":    "method1",
+						"type":    "function",
+						"inputs":  []interface{}{},
+						"outputs": []interface{}{},
+					},
+				},
+			}, body)
+
+			res := &http.Response{
+				Body: ioutil.NopCloser(bytes.NewReader([]byte(fftypes.JSONObject{
+					"approval": fftypes.JSONAny(`[]`),
+					"burn":     fftypes.JSONAny(`[]`),
+					"mint":     fftypes.JSONAny(`[]`),
+					"transfer": fftypes.JSONAny(`[]`),
+				}.String()))),
+				Header: http.Header{
+					"Content-Type": []string{"application/json"},
+				},
+				StatusCode: 200,
+			}
+			return res, nil
+		})
+
+	_, err := h.CheckInterface(context.Background(), pool, methods)
+	assert.NoError(t, err)
+}
+
+func TestCheckInterfaceABIBadInterface(t *testing.T) {
+	h, _, _, _, done := newTestFFTokens(t)
+	defer done()
+
+	pool := &core.TokenPool{
+		InterfaceFormat: "abi",
+		Locator:         "N1",
+	}
+	methods := []*fftypes.FFIMethod{
+		{Name: "method1", Params: fftypes.FFIParams{{Schema: fftypes.JSONAnyPtr("bad")}}},
+	}
+
+	_, err := h.CheckInterface(context.Background(), pool, methods)
+	assert.Regexp(t, "FF22052", err)
+}
+
+func TestCheckInterfaceFFI(t *testing.T) {
+	h, _, _, httpURL, done := newTestFFTokens(t)
+	defer done()
+
+	pool := &core.TokenPool{
+		InterfaceFormat: "ffi",
+		Locator:         "N1",
+	}
+	methods := []*fftypes.FFIMethod{
+		{Name: "method1"},
+	}
+
+	httpmock.RegisterResponder("POST", fmt.Sprintf("%s/api/v1/checkinterface", httpURL),
+		func(req *http.Request) (*http.Response, error) {
+			body := make(fftypes.JSONObject)
+			err := json.NewDecoder(req.Body).Decode(&body)
+			assert.NoError(t, err)
+			assert.Equal(t, fftypes.JSONObject{
+				"format":      "ffi",
+				"poolLocator": "N1",
+				"methods": []interface{}{
+					map[string]interface{}{
+						"name":        "method1",
+						"description": "",
+						"pathname":    "",
+						"params":      nil,
+						"returns":     nil,
+					},
+				},
+			}, body)
+
+			res := &http.Response{
+				Body: ioutil.NopCloser(bytes.NewReader([]byte(fftypes.JSONObject{
+					"approval": fftypes.JSONAny(`[]`),
+					"burn":     fftypes.JSONAny(`[]`),
+					"mint":     fftypes.JSONAny(`[]`),
+					"transfer": fftypes.JSONAny(`[]`),
+				}.String()))),
+				Header: http.Header{
+					"Content-Type": []string{"application/json"},
+				},
+				StatusCode: 200,
+			}
+			return res, nil
+		})
+
+	_, err := h.CheckInterface(context.Background(), pool, methods)
+	assert.NoError(t, err)
+}
+
+func TestCheckInterfaceFFIFail(t *testing.T) {
+	h, _, _, httpURL, done := newTestFFTokens(t)
+	defer done()
+
+	pool := &core.TokenPool{
+		InterfaceFormat: "ffi",
+		Locator:         "N1",
+	}
+	methods := []*fftypes.FFIMethod{
+		{Name: "method1"},
+	}
+
+	httpmock.RegisterResponder("POST", fmt.Sprintf("%s/api/v1/checkinterface", httpURL),
+		httpmock.NewJsonResponderOrPanic(500, fftypes.JSONObject{}))
+
+	_, err := h.CheckInterface(context.Background(), pool, methods)
+	assert.Regexp(t, "FF10274", err)
+}
+
+func TestCheckInterfaceFFIBadResponse(t *testing.T) {
+	h, _, _, httpURL, done := newTestFFTokens(t)
+	defer done()
+
+	pool := &core.TokenPool{
+		InterfaceFormat: "ffi",
+		Locator:         "N1",
+	}
+	methods := []*fftypes.FFIMethod{
+		{Name: "method1"},
+	}
+
+	httpmock.RegisterResponder("POST", fmt.Sprintf("%s/api/v1/checkinterface", httpURL),
+		func(req *http.Request) (*http.Response, error) {
+			res := &http.Response{
+				Body: ioutil.NopCloser(bytes.NewReader([]byte(fftypes.JSONObject{
+					"approval": map[bool]bool{true: false},
+				}.String()))),
+				Header: http.Header{
+					"Content-Type": []string{"application/json"},
+				},
+				StatusCode: 200,
+			}
+			return res, nil
+		})
+
+	_, err := h.CheckInterface(context.Background(), pool, methods)
+	assert.Regexp(t, "FF00127", err)
+}
diff --git a/test/data/contracts/erc1155/ERC1155Sample.json b/test/data/contracts/erc1155/ERC1155Sample.json
new file mode 100644
index 000000000..a51bbfc87
--- /dev/null
+++ b/test/data/contracts/erc1155/ERC1155Sample.json
@@ -0,0 +1,563 @@
+{
+  "_format": "hh-sol-artifact-1",
+  "contractName": "ERC1155MixedFungible",
+  "sourceName": "contracts/ERC1155MixedFungible.sol",
+  "abi": [
+    {
+      "inputs": [],
+      "stateMutability": "nonpayable",
+      "type": "constructor"
+    },
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "account",
+          "type": "address"
+        },
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "operator",
+          "type": "address"
+        },
+        {
+          "indexed": false,
+          "internalType": "bool",
+          "name": "approved",
+          "type": "bool"
+        }
+      ],
+      "name": "ApprovalForAll",
+      "type": "event"
+    },
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "operator",
+          "type": "address"
+        },
+        {
+          "indexed": true,
+          "internalType": "uint256",
+          "name": "type_id",
+          "type": "uint256"
+        },
+        {
+          "indexed": false,
+          "internalType": "bytes",
+          "name": "data",
+          "type": "bytes"
+        }
+      ],
+      "name": "TokenPoolCreation",
+      "type": "event"
+    },
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "operator",
+          "type": "address"
+        },
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "from",
+          "type": "address"
+        },
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "to",
+          "type": "address"
+        },
+        {
+          "indexed": false,
+          "internalType": "uint256[]",
+          "name": "ids",
+          "type": "uint256[]"
+        },
+        {
+          "indexed": false,
+          "internalType": "uint256[]",
+          "name": "values",
+          "type": "uint256[]"
+        }
+      ],
+      "name": "TransferBatch",
+      "type": "event"
+    },
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "operator",
+          "type": "address"
+        },
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "from",
+          "type": "address"
+        },
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "to",
+          "type": "address"
+        },
+        {
+          "indexed": false,
+          "internalType": "uint256",
+          "name": "id",
+          "type": "uint256"
+        },
+        {
+          "indexed": false,
+          "internalType": "uint256",
+          "name": "value",
+          "type": "uint256"
+        }
+      ],
+      "name": "TransferSingle",
+      "type": "event"
+    },
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": false,
+          "internalType": "string",
+          "name": "value",
+          "type": "string"
+        },
+        {
+          "indexed": true,
+          "internalType": "uint256",
+          "name": "id",
+          "type": "uint256"
+        }
+      ],
+      "name": "URI",
+      "type": "event"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "account",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "id",
+          "type": "uint256"
+        }
+      ],
+      "name": "balanceOf",
+      "outputs": [
+        {
+          "internalType": "uint256",
+          "name": "",
+          "type": "uint256"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address[]",
+          "name": "accounts",
+          "type": "address[]"
+        },
+        {
+          "internalType": "uint256[]",
+          "name": "ids",
+          "type": "uint256[]"
+        }
+      ],
+      "name": "balanceOfBatch",
+      "outputs": [
+        {
+          "internalType": "uint256[]",
+          "name": "",
+          "type": "uint256[]"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "account",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "id",
+          "type": "uint256"
+        },
+        {
+          "internalType": "uint256",
+          "name": "value",
+          "type": "uint256"
+        }
+      ],
+      "name": "burn",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "account",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256[]",
+          "name": "ids",
+          "type": "uint256[]"
+        },
+        {
+          "internalType": "uint256[]",
+          "name": "values",
+          "type": "uint256[]"
+        }
+      ],
+      "name": "burnBatch",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "bool",
+          "name": "is_fungible",
+          "type": "bool"
+        },
+        {
+          "internalType": "bytes",
+          "name": "data",
+          "type": "bytes"
+        }
+      ],
+      "name": "create",
+      "outputs": [
+        {
+          "internalType": "uint256",
+          "name": "type_id",
+          "type": "uint256"
+        }
+      ],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "uint256",
+          "name": "",
+          "type": "uint256"
+        }
+      ],
+      "name": "creators",
+      "outputs": [
+        {
+          "internalType": "address",
+          "name": "",
+          "type": "address"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "account",
+          "type": "address"
+        },
+        {
+          "internalType": "address",
+          "name": "operator",
+          "type": "address"
+        }
+      ],
+      "name": "isApprovedForAll",
+      "outputs": [
+        {
+          "internalType": "bool",
+          "name": "",
+          "type": "bool"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "uint256",
+          "name": "",
+          "type": "uint256"
+        }
+      ],
+      "name": "maxIndex",
+      "outputs": [
+        {
+          "internalType": "uint256",
+          "name": "",
+          "type": "uint256"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "uint256",
+          "name": "type_id",
+          "type": "uint256"
+        },
+        {
+          "internalType": "address[]",
+          "name": "to",
+          "type": "address[]"
+        },
+        {
+          "internalType": "uint256[]",
+          "name": "amounts",
+          "type": "uint256[]"
+        },
+        {
+          "internalType": "bytes",
+          "name": "data",
+          "type": "bytes"
+        }
+      ],
+      "name": "mintFungible",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "uint256",
+          "name": "type_id",
+          "type": "uint256"
+        },
+        {
+          "internalType": "address[]",
+          "name": "to",
+          "type": "address[]"
+        },
+        {
+          "internalType": "bytes",
+          "name": "data",
+          "type": "bytes"
+        }
+      ],
+      "name": "mintNonFungible",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "uint256",
+          "name": "type_id",
+          "type": "uint256"
+        },
+        {
+          "internalType": "address[]",
+          "name": "to",
+          "type": "address[]"
+        },
+        {
+          "internalType": "bytes",
+          "name": "data",
+          "type": "bytes"
+        },
+        {
+          "internalType": "string",
+          "name": "_uri",
+          "type": "string"
+        }
+      ],
+      "name": "mintNonFungibleWithURI",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "from",
+          "type": "address"
+        },
+        {
+          "internalType": "address",
+          "name": "to",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256[]",
+          "name": "ids",
+          "type": "uint256[]"
+        },
+        {
+          "internalType": "uint256[]",
+          "name": "amounts",
+          "type": "uint256[]"
+        },
+        {
+          "internalType": "bytes",
+          "name": "data",
+          "type": "bytes"
+        }
+      ],
+      "name": "safeBatchTransferFrom",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "from",
+          "type": "address"
+        },
+        {
+          "internalType": "address",
+          "name": "to",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "id",
+          "type": "uint256"
+        },
+        {
+          "internalType": "uint256",
+          "name": "amount",
+          "type": "uint256"
+        },
+        {
+          "internalType": "bytes",
+          "name": "data",
+          "type": "bytes"
+        }
+      ],
+      "name": "safeTransferFrom",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "operator",
+          "type": "address"
+        },
+        {
+          "internalType": "bool",
+          "name": "approved",
+          "type": "bool"
+        }
+      ],
+      "name": "setApprovalForAll",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "operator",
+          "type": "address"
+        },
+        {
+          "internalType": "bool",
+          "name": "approved",
+          "type": "bool"
+        },
+        {
+          "internalType": "bytes",
+          "name": "data",
+          "type": "bytes"
+        }
+      ],
+      "name": "setApprovalForAllWithData",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "bytes4",
+          "name": "interfaceId",
+          "type": "bytes4"
+        }
+      ],
+      "name": "supportsInterface",
+      "outputs": [
+        {
+          "internalType": "bool",
+          "name": "",
+          "type": "bool"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "uint256",
+          "name": "id",
+          "type": "uint256"
+        }
+      ],
+      "name": "uri",
+      "outputs": [
+        {
+          "internalType": "string",
+          "name": "",
+          "type": "string"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    }
+  ],
+  "bytecode": "0x60806040523480156200001157600080fd5b506040518060200160405280600081525062000033816200003a60201b60201c565b506200016b565b80600290805190602001906200005292919062000056565b5050565b828054620000649062000106565b90600052602060002090601f016020900481019282620000885760008555620000d4565b82601f10620000a357805160ff1916838001178555620000d4565b82800160010185558215620000d4579182015b82811115620000d3578251825591602001919060010190620000b6565b5b509050620000e39190620000e7565b5090565b5b8082111562000102576000816000905550600101620000e8565b5090565b600060028204905060018216806200011f57607f821691505b602082108114156200013657620001356200013c565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b614312806200017b6000396000f3fe608060405234801561001057600080fd5b506004361061010a5760003560e01c806354dbdbf4116100a2578063a22cb46511610071578063a22cb465146102d7578063cd53d08e146102f3578063e985e9c514610323578063f242432a14610353578063f5298aca1461036f5761010a565b806354dbdbf4146102535780636b20c4541461026f57806371cc16561461028b5780637e53b65d146102bb5761010a565b8063183fb254116100de578063183fb254146101cf5780631cbf311d146101eb5780632eb2c2d6146102075780634e1273f4146102235761010a565b8062fdd58e1461010f57806301ffc9a71461013f57806308d7d4691461016f5780630e89341c1461019f575b600080fd5b61012960048036038101906101249190612e0a565b61038b565b60405161013691906138e1565b60405180910390f35b61015960048036038101906101549190612f59565b610454565b6040516101669190613680565b60405180910390f35b61018960048036038101906101849190612fab565b610536565b60405161019691906138e1565b60405180910390f35b6101b960048036038101906101b49190612fab565b61054e565b6040516101c691906136bf565b60405180910390f35b6101e960048036038101906101e49190612fd4565b610623565b005b61020560048036038101906102009190612d9e565b61082c565b005b610221600480360381019061021c9190612b95565b61083c565b005b61023d60048036038101906102389190612e95565b6108dd565b60405161024a9190613627565b60405180910390f35b61026d6004803603810190610268919061308d565b610a8e565b005b61028960048036038101906102849190612ce3565b610c78565b005b6102a560048036038101906102a09190612f01565b610d15565b6040516102b291906138e1565b60405180910390f35b6102d560048036038101906102d09190613116565b610e15565b005b6102f160048036038101906102ec9190612d62565b611011565b005b61030d60048036038101906103089190612fab565b611027565b60405161031a919061354a565b60405180910390f35b61033d60048036038101906103389190612b59565b61105a565b60405161034a9190613680565b60405180910390f35b61036d60048036038101906103689190612c54565b6110ee565b005b61038960048036038101906103849190612e46565b61118f565b005b60008073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614156103fc576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103f390613721565b60405180910390fd5b60008083815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b60007fd9b67a26000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916148061051f57507f0e89341c000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916145b8061052f575061052e8261122c565b5b9050919050565b60056020528060005260406000206000915090505481565b6060600060066000848152602001908152602001600020805461057090613b81565b80601f016020809104026020016040519081016040528092919081815260200182805461059c90613b81565b80156105e95780601f106105be576101008083540402835291602001916105e9565b820191906000526020600020905b8154815290600101906020018083116105cc57829003601f168201915b50505050509050600081905060008151141561061857604051806020016040528060008152509250505061061e565b81925050505b919050565b8661062c611296565b73ffffffffffffffffffffffffffffffffffffffff166004600083815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161461069657600080fd5b61069f8861129e565b6106de576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106d5906137a1565b60405180910390fd5b848490508787905014610726576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161071d90613801565b60405180910390fd5b60005b8787905081101561082157610810888883818110610770577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b90506020020160208101906107859190612b30565b8a8888858181106107bf577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b9050602002013587878080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050506112cc565b8061081a90613be4565b9050610729565b505050505050505050565b6108368484611011565b50505050565b610844611296565b73ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff16148061088a575061088985610884611296565b61105a565b5b6108c9576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016108c0906137e1565b60405180910390fd5b6108d6858585858561147d565b5050505050565b60608151835114610923576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161091a90613881565b60405180910390fd5b6000835167ffffffffffffffff811115610966577f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040519080825280602002602001820160405280156109945781602001602082028036833780820191505090505b50905060005b8451811015610a8357610a2d8582815181106109df577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6020026020010151858381518110610a20577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b602002602001015161038b565b828281518110610a66577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60200260200101818152505080610a7c90613be4565b905061099a565b508091505092915050565b84610a97611296565b73ffffffffffffffffffffffffffffffffffffffff166004600083815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614610b0157600080fd5b610b0a866117eb565b610b49576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610b4090613781565b60405180910390fd5b600060016005600089815260200190815260200160002054610b6b9190613a75565b9050610b8d86869050600560008a815260200190815260200160002054611819565b600560008981526020019081526020016000208190555060005b86869050811015610c6e57610c5d878783818110610bee577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b9050602002016020810190610c039190612b30565b8284610c0f9190613a75565b8a17600188888080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050506112cc565b80610c6790613be4565b9050610ba7565b5050505050505050565b610c80611296565b73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161480610cc65750610cc583610cc0611296565b61105a565b5b610d05576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610cfc90613761565b60405180910390fd5b610d1083838361182f565b505050565b60006080600360008154610d2890613be4565b919050819055901b905083610d5d577f8000000000000000000000000000000000000000000000000000000000000000811790505b610d65611296565b6004600083815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555080610dbf611296565b73ffffffffffffffffffffffffffffffffffffffff167f0d2ce24070e07853454690ae0a0416ddd4845188f8dbfe0ef3a761acb3d2e34e8585604051610e0692919061369b565b60405180910390a39392505050565b85610e1e611296565b73ffffffffffffffffffffffffffffffffffffffff166004600083815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614610e8857600080fd5b610e91876117eb565b610ed0576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610ec790613781565b60405180910390fd5b60006001600560008a815260200190815260200160002054610ef29190613a75565b9050610f1487879050600560008b815260200190815260200160002054611819565b600560008a81526020019081526020016000208190555060005b878790508110156110065760008183610f479190613a75565b8a179050610fe9898984818110610f87577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b9050602002016020810190610f9c9190612b30565b8260018a8a8080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050506112cc565b610ff48a8287611b4a565b5080610fff90613be4565b9050610f2e565b505050505050505050565b61102361101c611296565b8383611c33565b5050565b60046020528060005260406000206000915054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16905092915050565b6110f6611296565b73ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff16148061113c575061113b85611136611296565b61105a565b5b61117b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161117290613761565b60405180910390fd5b6111888585858585611da0565b5050505050565b611197611296565b73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614806111dd57506111dc836111d7611296565b61105a565b5b61121c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161121390613761565b60405180910390fd5b61122783838361203c565b505050565b60007f01ffc9a7000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916149050919050565b600033905090565b6000807f80000000000000000000000000000000000000000000000000000000000000008316149050919050565b600073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16141561133c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611333906138c1565b60405180910390fd5b6000611346611296565b9050600061135385612283565b9050600061136085612283565b905061137183600089858589612349565b8460008088815260200190815260200160002060008973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546113d09190613a75565b925050819055508673ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62898960405161144e9291906138fc565b60405180910390a461146583600089858589612351565b61147483600089898989612359565b50505050505050565b81518351146114c1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016114b8906138a1565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161415611531576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611528906137c1565b60405180910390fd5b600061153b611296565b905061154b818787878787612349565b60005b8451811015611748576000858281518110611592577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6020026020010151905060008583815181106115d7577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60200260200101519050600080600084815260200190815260200160002060008b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905081811015611678576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161166f90613841565b60405180910390fd5b81810360008085815260200190815260200160002060008c73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508160008085815260200190815260200160002060008b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825461172d9190613a75565b925050819055505050508061174190613be4565b905061154e565b508473ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff167f4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb87876040516117bf929190613649565b60405180910390a46117d5818787878787612351565b6117e3818787878787612540565b505050505050565b60007f8000000000000000000000000000000000000000000000000000000000000000808316149050919050565b600081836118279190613a75565b905092915050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141561189f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161189690613821565b60405180910390fd5b80518251146118e3576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016118da906138a1565b60405180910390fd5b60006118ed611296565b905061190d81856000868660405180602001604052806000815250612349565b60005b8351811015611aa6576000848281518110611954577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b602002602001015190506000848381518110611999577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60200260200101519050600080600084815260200190815260200160002060008973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905081811015611a3a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611a3190613741565b60405180910390fd5b81810360008085815260200190815260200160002060008a73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055505050508080611a9e90613be4565b915050611910565b50600073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff167f4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb8686604051611b1e929190613649565b60405180910390a4611b4481856000868660405180602001604052806000815250612351565b50505050565b82611b53611296565b73ffffffffffffffffffffffffffffffffffffffff166004600083815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614611bbd57600080fd5b611bc6846117eb565b611c05576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611bfc90613781565b60405180910390fd5b81600660008581526020019081526020016000209080519060200190611c2c92919061274a565b5050505050565b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415611ca2576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611c9990613861565b60405180910390fd5b80600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff0219169083151502179055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c3183604051611d939190613680565b60405180910390a3505050565b600073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161415611e10576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611e07906137c1565b60405180910390fd5b6000611e1a611296565b90506000611e2785612283565b90506000611e3485612283565b9050611e44838989858589612349565b600080600088815260200190815260200160002060008a73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905085811015611edb576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611ed290613841565b60405180910390fd5b85810360008089815260200190815260200160002060008b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508560008089815260200190815260200160002060008a73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254611f909190613a75565b925050819055508773ffffffffffffffffffffffffffffffffffffffff168973ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff167fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f628a8a60405161200d9291906138fc565b60405180910390a4612023848a8a86868a612351565b612031848a8a8a8a8a612359565b505050505050505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614156120ac576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016120a390613821565b60405180910390fd5b60006120b6611296565b905060006120c384612283565b905060006120d084612283565b90506120f083876000858560405180602001604052806000815250612349565b600080600087815260200190815260200160002060008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905084811015612187576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161217e90613741565b60405180910390fd5b84810360008088815260200190815260200160002060008973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550600073ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff167fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6289896040516122549291906138fc565b60405180910390a461227a84886000868660405180602001604052806000815250612351565b50505050505050565b60606000600167ffffffffffffffff8111156122c8577f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040519080825280602002602001820160405280156122f65781602001602082028036833780820191505090505b5090508281600081518110612334577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60200260200101818152505080915050919050565b505050505050565b505050505050565b6123788473ffffffffffffffffffffffffffffffffffffffff16612727565b15612538578373ffffffffffffffffffffffffffffffffffffffff1663f23a6e6187878686866040518663ffffffff1660e01b81526004016123be9594939291906135cd565b602060405180830381600087803b1580156123d857600080fd5b505af192505050801561240957506040513d601f19601f820116820180604052508101906124069190612f82565b60015b6124af57612415613cba565b806308c379a01415612472575061242a6141ea565b806124355750612474565b806040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161246991906136bf565b60405180910390fd5b505b6040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016124a6906136e1565b60405180910390fd5b63f23a6e6160e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614612536576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161252d90613701565b60405180910390fd5b505b505050505050565b61255f8473ffffffffffffffffffffffffffffffffffffffff16612727565b1561271f578373ffffffffffffffffffffffffffffffffffffffff1663bc197c8187878686866040518663ffffffff1660e01b81526004016125a5959493929190613565565b602060405180830381600087803b1580156125bf57600080fd5b505af19250505080156125f057506040513d601f19601f820116820180604052508101906125ed9190612f82565b60015b612696576125fc613cba565b806308c379a0141561265957506126116141ea565b8061261c575061265b565b806040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161265091906136bf565b60405180910390fd5b505b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161268d906136e1565b60405180910390fd5b63bc197c8160e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19161461271d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161271490613701565b60405180910390fd5b505b505050505050565b6000808273ffffffffffffffffffffffffffffffffffffffff163b119050919050565b82805461275690613b81565b90600052602060002090601f01602090048101928261277857600085556127bf565b82601f1061279157805160ff19168380011785556127bf565b828001600101855582156127bf579182015b828111156127be5782518255916020019190600101906127a3565b5b5090506127cc91906127d0565b5090565b5b808211156127e95760008160009055506001016127d1565b5090565b60006128006127fb8461394a565b613925565b9050808382526020820190508285602086028201111561281f57600080fd5b60005b8581101561284f57816128358882612941565b845260208401935060208301925050600181019050612822565b5050509392505050565b600061286c61286784613976565b613925565b9050808382526020820190508285602086028201111561288b57600080fd5b60005b858110156128bb57816128a18882612b1b565b84526020840193506020830192505060018101905061288e565b5050509392505050565b60006128d86128d3846139a2565b613925565b9050828152602081018484840111156128f057600080fd5b6128fb848285613b3f565b509392505050565b6000612916612911846139d3565b613925565b90508281526020810184848401111561292e57600080fd5b612939848285613b3f565b509392505050565b60008135905061295081614280565b92915050565b60008083601f84011261296857600080fd5b8235905067ffffffffffffffff81111561298157600080fd5b60208301915083602082028301111561299957600080fd5b9250929050565b600082601f8301126129b157600080fd5b81356129c18482602086016127ed565b91505092915050565b60008083601f8401126129dc57600080fd5b8235905067ffffffffffffffff8111156129f557600080fd5b602083019150836020820283011115612a0d57600080fd5b9250929050565b600082601f830112612a2557600080fd5b8135612a35848260208601612859565b91505092915050565b600081359050612a4d81614297565b92915050565b600081359050612a62816142ae565b92915050565b600081519050612a77816142ae565b92915050565b60008083601f840112612a8f57600080fd5b8235905067ffffffffffffffff811115612aa857600080fd5b602083019150836001820283011115612ac057600080fd5b9250929050565b600082601f830112612ad857600080fd5b8135612ae88482602086016128c5565b91505092915050565b600082601f830112612b0257600080fd5b8135612b12848260208601612903565b91505092915050565b600081359050612b2a816142c5565b92915050565b600060208284031215612b4257600080fd5b6000612b5084828501612941565b91505092915050565b60008060408385031215612b6c57600080fd5b6000612b7a85828601612941565b9250506020612b8b85828601612941565b9150509250929050565b600080600080600060a08688031215612bad57600080fd5b6000612bbb88828901612941565b9550506020612bcc88828901612941565b945050604086013567ffffffffffffffff811115612be957600080fd5b612bf588828901612a14565b935050606086013567ffffffffffffffff811115612c1257600080fd5b612c1e88828901612a14565b925050608086013567ffffffffffffffff811115612c3b57600080fd5b612c4788828901612ac7565b9150509295509295909350565b600080600080600060a08688031215612c6c57600080fd5b6000612c7a88828901612941565b9550506020612c8b88828901612941565b9450506040612c9c88828901612b1b565b9350506060612cad88828901612b1b565b925050608086013567ffffffffffffffff811115612cca57600080fd5b612cd688828901612ac7565b9150509295509295909350565b600080600060608486031215612cf857600080fd5b6000612d0686828701612941565b935050602084013567ffffffffffffffff811115612d2357600080fd5b612d2f86828701612a14565b925050604084013567ffffffffffffffff811115612d4c57600080fd5b612d5886828701612a14565b9150509250925092565b60008060408385031215612d7557600080fd5b6000612d8385828601612941565b9250506020612d9485828601612a3e565b9150509250929050565b60008060008060608587031215612db457600080fd5b6000612dc287828801612941565b9450506020612dd387828801612a3e565b935050604085013567ffffffffffffffff811115612df057600080fd5b612dfc87828801612a7d565b925092505092959194509250565b60008060408385031215612e1d57600080fd5b6000612e2b85828601612941565b9250506020612e3c85828601612b1b565b9150509250929050565b600080600060608486031215612e5b57600080fd5b6000612e6986828701612941565b9350506020612e7a86828701612b1b565b9250506040612e8b86828701612b1b565b9150509250925092565b60008060408385031215612ea857600080fd5b600083013567ffffffffffffffff811115612ec257600080fd5b612ece858286016129a0565b925050602083013567ffffffffffffffff811115612eeb57600080fd5b612ef785828601612a14565b9150509250929050565b600080600060408486031215612f1657600080fd5b6000612f2486828701612a3e565b935050602084013567ffffffffffffffff811115612f4157600080fd5b612f4d86828701612a7d565b92509250509250925092565b600060208284031215612f6b57600080fd5b6000612f7984828501612a53565b91505092915050565b600060208284031215612f9457600080fd5b6000612fa284828501612a68565b91505092915050565b600060208284031215612fbd57600080fd5b6000612fcb84828501612b1b565b91505092915050565b60008060008060008060006080888a031215612fef57600080fd5b6000612ffd8a828b01612b1b565b975050602088013567ffffffffffffffff81111561301a57600080fd5b6130268a828b01612956565b9650965050604088013567ffffffffffffffff81111561304557600080fd5b6130518a828b016129ca565b9450945050606088013567ffffffffffffffff81111561307057600080fd5b61307c8a828b01612a7d565b925092505092959891949750929550565b6000806000806000606086880312156130a557600080fd5b60006130b388828901612b1b565b955050602086013567ffffffffffffffff8111156130d057600080fd5b6130dc88828901612956565b9450945050604086013567ffffffffffffffff8111156130fb57600080fd5b61310788828901612a7d565b92509250509295509295909350565b6000806000806000806080878903121561312f57600080fd5b600061313d89828a01612b1b565b965050602087013567ffffffffffffffff81111561315a57600080fd5b61316689828a01612956565b9550955050604087013567ffffffffffffffff81111561318557600080fd5b61319189828a01612a7d565b9350935050606087013567ffffffffffffffff8111156131b057600080fd5b6131bc89828a01612af1565b9150509295509295509295565b60006131d5838361352c565b60208301905092915050565b6131ea81613acb565b82525050565b60006131fb82613a14565b6132058185613a42565b935061321083613a04565b8060005b8381101561324157815161322888826131c9565b975061323383613a35565b925050600181019050613214565b5085935050505092915050565b61325781613add565b82525050565b60006132698385613a53565b9350613276838584613b3f565b61327f83613cdc565b840190509392505050565b600061329582613a1f565b61329f8185613a53565b93506132af818560208601613b4e565b6132b881613cdc565b840191505092915050565b60006132ce82613a2a565b6132d88185613a64565b93506132e8818560208601613b4e565b6132f181613cdc565b840191505092915050565b6000613309603483613a64565b915061331482613cfa565b604082019050919050565b600061332c602883613a64565b915061333782613d49565b604082019050919050565b600061334f602b83613a64565b915061335a82613d98565b604082019050919050565b6000613372602483613a64565b915061337d82613de7565b604082019050919050565b6000613395602983613a64565b91506133a082613e36565b604082019050919050565b60006133b8603f83613a64565b91506133c382613e85565b604082019050919050565b60006133db603b83613a64565b91506133e682613ed4565b604082019050919050565b60006133fe602583613a64565b915061340982613f23565b604082019050919050565b6000613421603283613a64565b915061342c82613f72565b604082019050919050565b6000613444603483613a64565b915061344f82613fc1565b604082019050919050565b6000613467602383613a64565b915061347282614010565b604082019050919050565b600061348a602a83613a64565b91506134958261405f565b604082019050919050565b60006134ad602983613a64565b91506134b8826140ae565b604082019050919050565b60006134d0602983613a64565b91506134db826140fd565b604082019050919050565b60006134f3602883613a64565b91506134fe8261414c565b604082019050919050565b6000613516602183613a64565b91506135218261419b565b604082019050919050565b61353581613b35565b82525050565b61354481613b35565b82525050565b600060208201905061355f60008301846131e1565b92915050565b600060a08201905061357a60008301886131e1565b61358760208301876131e1565b818103604083015261359981866131f0565b905081810360608301526135ad81856131f0565b905081810360808301526135c1818461328a565b90509695505050505050565b600060a0820190506135e260008301886131e1565b6135ef60208301876131e1565b6135fc604083018661353b565b613609606083018561353b565b818103608083015261361b818461328a565b90509695505050505050565b6000602082019050818103600083015261364181846131f0565b905092915050565b6000604082019050818103600083015261366381856131f0565b9050818103602083015261367781846131f0565b90509392505050565b6000602082019050613695600083018461324e565b92915050565b600060208201905081810360008301526136b681848661325d565b90509392505050565b600060208201905081810360008301526136d981846132c3565b905092915050565b600060208201905081810360008301526136fa816132fc565b9050919050565b6000602082019050818103600083015261371a8161331f565b9050919050565b6000602082019050818103600083015261373a81613342565b9050919050565b6000602082019050818103600083015261375a81613365565b9050919050565b6000602082019050818103600083015261377a81613388565b9050919050565b6000602082019050818103600083015261379a816133ab565b9050919050565b600060208201905081810360008301526137ba816133ce565b9050919050565b600060208201905081810360008301526137da816133f1565b9050919050565b600060208201905081810360008301526137fa81613414565b9050919050565b6000602082019050818103600083015261381a81613437565b9050919050565b6000602082019050818103600083015261383a8161345a565b9050919050565b6000602082019050818103600083015261385a8161347d565b9050919050565b6000602082019050818103600083015261387a816134a0565b9050919050565b6000602082019050818103600083015261389a816134c3565b9050919050565b600060208201905081810360008301526138ba816134e6565b9050919050565b600060208201905081810360008301526138da81613509565b9050919050565b60006020820190506138f6600083018461353b565b92915050565b6000604082019050613911600083018561353b565b61391e602083018461353b565b9392505050565b600061392f613940565b905061393b8282613bb3565b919050565b6000604051905090565b600067ffffffffffffffff82111561396557613964613c8b565b5b602082029050602081019050919050565b600067ffffffffffffffff82111561399157613990613c8b565b5b602082029050602081019050919050565b600067ffffffffffffffff8211156139bd576139bc613c8b565b5b6139c682613cdc565b9050602081019050919050565b600067ffffffffffffffff8211156139ee576139ed613c8b565b5b6139f782613cdc565b9050602081019050919050565b6000819050602082019050919050565b600081519050919050565b600081519050919050565b600081519050919050565b6000602082019050919050565b600082825260208201905092915050565b600082825260208201905092915050565b600082825260208201905092915050565b6000613a8082613b35565b9150613a8b83613b35565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115613ac057613abf613c2d565b5b828201905092915050565b6000613ad682613b15565b9050919050565b60008115159050919050565b60007fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b82818337600083830152505050565b60005b83811015613b6c578082015181840152602081019050613b51565b83811115613b7b576000848401525b50505050565b60006002820490506001821680613b9957607f821691505b60208210811415613bad57613bac613c5c565b5b50919050565b613bbc82613cdc565b810181811067ffffffffffffffff82111715613bdb57613bda613c8b565b5b80604052505050565b6000613bef82613b35565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff821415613c2257613c21613c2d565b5b600182019050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600060033d1115613cd95760046000803e613cd6600051613ced565b90505b90565b6000601f19601f8301169050919050565b60008160e01c9050919050565b7f455243313135353a207472616e7366657220746f206e6f6e204552433131353560008201527f526563656976657220696d706c656d656e746572000000000000000000000000602082015250565b7f455243313135353a204552433131353552656365697665722072656a6563746560008201527f6420746f6b656e73000000000000000000000000000000000000000000000000602082015250565b7f455243313135353a2062616c616e636520717565727920666f7220746865207a60008201527f65726f2061646472657373000000000000000000000000000000000000000000602082015250565b7f455243313135353a206275726e20616d6f756e7420657863656564732062616c60008201527f616e636500000000000000000000000000000000000000000000000000000000602082015250565b7f455243313135353a2063616c6c6572206973206e6f74206f776e6572206e6f7260008201527f20617070726f7665640000000000000000000000000000000000000000000000602082015250565b7f455243313135354d6978656446756e6769626c653a20696420646f6573206e6f60008201527f7420726570726573656e742061206e6f6e2d66756e6769626c65207479706500602082015250565b7f455243313135354d6978656446756e6769626c653a20696420646f6573206e6f60008201527f7420726570726573656e7420612066756e6769626c6520747970650000000000602082015250565b7f455243313135353a207472616e7366657220746f20746865207a65726f20616460008201527f6472657373000000000000000000000000000000000000000000000000000000602082015250565b7f455243313135353a207472616e736665722063616c6c6572206973206e6f742060008201527f6f776e6572206e6f7220617070726f7665640000000000000000000000000000602082015250565b7f455243313135354d6978656446756e6769626c653a20746f20616e6420616d6f60008201527f756e7473206c656e677468206d69736d61746368000000000000000000000000602082015250565b7f455243313135353a206275726e2066726f6d20746865207a65726f206164647260008201527f6573730000000000000000000000000000000000000000000000000000000000602082015250565b7f455243313135353a20696e73756666696369656e742062616c616e636520666f60008201527f72207472616e7366657200000000000000000000000000000000000000000000602082015250565b7f455243313135353a2073657474696e6720617070726f76616c2073746174757360008201527f20666f722073656c660000000000000000000000000000000000000000000000602082015250565b7f455243313135353a206163636f756e747320616e6420696473206c656e67746860008201527f206d69736d617463680000000000000000000000000000000000000000000000602082015250565b7f455243313135353a2069647320616e6420616d6f756e7473206c656e6774682060008201527f6d69736d61746368000000000000000000000000000000000000000000000000602082015250565b7f455243313135353a206d696e7420746f20746865207a65726f2061646472657360008201527f7300000000000000000000000000000000000000000000000000000000000000602082015250565b600060443d10156141fa5761427d565b614202613940565b60043d036004823e80513d602482011167ffffffffffffffff8211171561422a57505061427d565b808201805167ffffffffffffffff811115614248575050505061427d565b80602083010160043d03850181111561426557505050505061427d565b61427482602001850186613bb3565b82955050505050505b90565b61428981613acb565b811461429457600080fd5b50565b6142a081613add565b81146142ab57600080fd5b50565b6142b781613ae9565b81146142c257600080fd5b50565b6142ce81613b35565b81146142d957600080fd5b5056fea2646970667358221220294807453883ec73af4b9e8ca1836696c00f88042e85782bf38ba686bd810b9364736f6c63430008040033",
+  "deployedBytecode": "0x608060405234801561001057600080fd5b506004361061010a5760003560e01c806354dbdbf4116100a2578063a22cb46511610071578063a22cb465146102d7578063cd53d08e146102f3578063e985e9c514610323578063f242432a14610353578063f5298aca1461036f5761010a565b806354dbdbf4146102535780636b20c4541461026f57806371cc16561461028b5780637e53b65d146102bb5761010a565b8063183fb254116100de578063183fb254146101cf5780631cbf311d146101eb5780632eb2c2d6146102075780634e1273f4146102235761010a565b8062fdd58e1461010f57806301ffc9a71461013f57806308d7d4691461016f5780630e89341c1461019f575b600080fd5b61012960048036038101906101249190612e0a565b61038b565b60405161013691906138e1565b60405180910390f35b61015960048036038101906101549190612f59565b610454565b6040516101669190613680565b60405180910390f35b61018960048036038101906101849190612fab565b610536565b60405161019691906138e1565b60405180910390f35b6101b960048036038101906101b49190612fab565b61054e565b6040516101c691906136bf565b60405180910390f35b6101e960048036038101906101e49190612fd4565b610623565b005b61020560048036038101906102009190612d9e565b61082c565b005b610221600480360381019061021c9190612b95565b61083c565b005b61023d60048036038101906102389190612e95565b6108dd565b60405161024a9190613627565b60405180910390f35b61026d6004803603810190610268919061308d565b610a8e565b005b61028960048036038101906102849190612ce3565b610c78565b005b6102a560048036038101906102a09190612f01565b610d15565b6040516102b291906138e1565b60405180910390f35b6102d560048036038101906102d09190613116565b610e15565b005b6102f160048036038101906102ec9190612d62565b611011565b005b61030d60048036038101906103089190612fab565b611027565b60405161031a919061354a565b60405180910390f35b61033d60048036038101906103389190612b59565b61105a565b60405161034a9190613680565b60405180910390f35b61036d60048036038101906103689190612c54565b6110ee565b005b61038960048036038101906103849190612e46565b61118f565b005b60008073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614156103fc576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103f390613721565b60405180910390fd5b60008083815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b60007fd9b67a26000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916148061051f57507f0e89341c000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916145b8061052f575061052e8261122c565b5b9050919050565b60056020528060005260406000206000915090505481565b6060600060066000848152602001908152602001600020805461057090613b81565b80601f016020809104026020016040519081016040528092919081815260200182805461059c90613b81565b80156105e95780601f106105be576101008083540402835291602001916105e9565b820191906000526020600020905b8154815290600101906020018083116105cc57829003601f168201915b50505050509050600081905060008151141561061857604051806020016040528060008152509250505061061e565b81925050505b919050565b8661062c611296565b73ffffffffffffffffffffffffffffffffffffffff166004600083815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161461069657600080fd5b61069f8861129e565b6106de576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106d5906137a1565b60405180910390fd5b848490508787905014610726576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161071d90613801565b60405180910390fd5b60005b8787905081101561082157610810888883818110610770577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b90506020020160208101906107859190612b30565b8a8888858181106107bf577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b9050602002013587878080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050506112cc565b8061081a90613be4565b9050610729565b505050505050505050565b6108368484611011565b50505050565b610844611296565b73ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff16148061088a575061088985610884611296565b61105a565b5b6108c9576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016108c0906137e1565b60405180910390fd5b6108d6858585858561147d565b5050505050565b60608151835114610923576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161091a90613881565b60405180910390fd5b6000835167ffffffffffffffff811115610966577f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040519080825280602002602001820160405280156109945781602001602082028036833780820191505090505b50905060005b8451811015610a8357610a2d8582815181106109df577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6020026020010151858381518110610a20577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b602002602001015161038b565b828281518110610a66577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60200260200101818152505080610a7c90613be4565b905061099a565b508091505092915050565b84610a97611296565b73ffffffffffffffffffffffffffffffffffffffff166004600083815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614610b0157600080fd5b610b0a866117eb565b610b49576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610b4090613781565b60405180910390fd5b600060016005600089815260200190815260200160002054610b6b9190613a75565b9050610b8d86869050600560008a815260200190815260200160002054611819565b600560008981526020019081526020016000208190555060005b86869050811015610c6e57610c5d878783818110610bee577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b9050602002016020810190610c039190612b30565b8284610c0f9190613a75565b8a17600188888080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050506112cc565b80610c6790613be4565b9050610ba7565b5050505050505050565b610c80611296565b73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161480610cc65750610cc583610cc0611296565b61105a565b5b610d05576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610cfc90613761565b60405180910390fd5b610d1083838361182f565b505050565b60006080600360008154610d2890613be4565b919050819055901b905083610d5d577f8000000000000000000000000000000000000000000000000000000000000000811790505b610d65611296565b6004600083815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555080610dbf611296565b73ffffffffffffffffffffffffffffffffffffffff167f0d2ce24070e07853454690ae0a0416ddd4845188f8dbfe0ef3a761acb3d2e34e8585604051610e0692919061369b565b60405180910390a39392505050565b85610e1e611296565b73ffffffffffffffffffffffffffffffffffffffff166004600083815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614610e8857600080fd5b610e91876117eb565b610ed0576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610ec790613781565b60405180910390fd5b60006001600560008a815260200190815260200160002054610ef29190613a75565b9050610f1487879050600560008b815260200190815260200160002054611819565b600560008a81526020019081526020016000208190555060005b878790508110156110065760008183610f479190613a75565b8a179050610fe9898984818110610f87577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b9050602002016020810190610f9c9190612b30565b8260018a8a8080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050506112cc565b610ff48a8287611b4a565b5080610fff90613be4565b9050610f2e565b505050505050505050565b61102361101c611296565b8383611c33565b5050565b60046020528060005260406000206000915054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16905092915050565b6110f6611296565b73ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff16148061113c575061113b85611136611296565b61105a565b5b61117b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161117290613761565b60405180910390fd5b6111888585858585611da0565b5050505050565b611197611296565b73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614806111dd57506111dc836111d7611296565b61105a565b5b61121c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161121390613761565b60405180910390fd5b61122783838361203c565b505050565b60007f01ffc9a7000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916149050919050565b600033905090565b6000807f80000000000000000000000000000000000000000000000000000000000000008316149050919050565b600073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16141561133c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611333906138c1565b60405180910390fd5b6000611346611296565b9050600061135385612283565b9050600061136085612283565b905061137183600089858589612349565b8460008088815260200190815260200160002060008973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546113d09190613a75565b925050819055508673ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62898960405161144e9291906138fc565b60405180910390a461146583600089858589612351565b61147483600089898989612359565b50505050505050565b81518351146114c1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016114b8906138a1565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161415611531576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611528906137c1565b60405180910390fd5b600061153b611296565b905061154b818787878787612349565b60005b8451811015611748576000858281518110611592577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6020026020010151905060008583815181106115d7577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60200260200101519050600080600084815260200190815260200160002060008b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905081811015611678576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161166f90613841565b60405180910390fd5b81810360008085815260200190815260200160002060008c73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508160008085815260200190815260200160002060008b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825461172d9190613a75565b925050819055505050508061174190613be4565b905061154e565b508473ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff167f4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb87876040516117bf929190613649565b60405180910390a46117d5818787878787612351565b6117e3818787878787612540565b505050505050565b60007f8000000000000000000000000000000000000000000000000000000000000000808316149050919050565b600081836118279190613a75565b905092915050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141561189f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161189690613821565b60405180910390fd5b80518251146118e3576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016118da906138a1565b60405180910390fd5b60006118ed611296565b905061190d81856000868660405180602001604052806000815250612349565b60005b8351811015611aa6576000848281518110611954577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b602002602001015190506000848381518110611999577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60200260200101519050600080600084815260200190815260200160002060008973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905081811015611a3a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611a3190613741565b60405180910390fd5b81810360008085815260200190815260200160002060008a73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055505050508080611a9e90613be4565b915050611910565b50600073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff167f4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb8686604051611b1e929190613649565b60405180910390a4611b4481856000868660405180602001604052806000815250612351565b50505050565b82611b53611296565b73ffffffffffffffffffffffffffffffffffffffff166004600083815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614611bbd57600080fd5b611bc6846117eb565b611c05576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611bfc90613781565b60405180910390fd5b81600660008581526020019081526020016000209080519060200190611c2c92919061274a565b5050505050565b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415611ca2576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611c9990613861565b60405180910390fd5b80600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff0219169083151502179055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c3183604051611d939190613680565b60405180910390a3505050565b600073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161415611e10576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611e07906137c1565b60405180910390fd5b6000611e1a611296565b90506000611e2785612283565b90506000611e3485612283565b9050611e44838989858589612349565b600080600088815260200190815260200160002060008a73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905085811015611edb576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611ed290613841565b60405180910390fd5b85810360008089815260200190815260200160002060008b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508560008089815260200190815260200160002060008a73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254611f909190613a75565b925050819055508773ffffffffffffffffffffffffffffffffffffffff168973ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff167fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f628a8a60405161200d9291906138fc565b60405180910390a4612023848a8a86868a612351565b612031848a8a8a8a8a612359565b505050505050505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614156120ac576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016120a390613821565b60405180910390fd5b60006120b6611296565b905060006120c384612283565b905060006120d084612283565b90506120f083876000858560405180602001604052806000815250612349565b600080600087815260200190815260200160002060008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905084811015612187576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161217e90613741565b60405180910390fd5b84810360008088815260200190815260200160002060008973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550600073ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff167fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6289896040516122549291906138fc565b60405180910390a461227a84886000868660405180602001604052806000815250612351565b50505050505050565b60606000600167ffffffffffffffff8111156122c8577f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040519080825280602002602001820160405280156122f65781602001602082028036833780820191505090505b5090508281600081518110612334577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60200260200101818152505080915050919050565b505050505050565b505050505050565b6123788473ffffffffffffffffffffffffffffffffffffffff16612727565b15612538578373ffffffffffffffffffffffffffffffffffffffff1663f23a6e6187878686866040518663ffffffff1660e01b81526004016123be9594939291906135cd565b602060405180830381600087803b1580156123d857600080fd5b505af192505050801561240957506040513d601f19601f820116820180604052508101906124069190612f82565b60015b6124af57612415613cba565b806308c379a01415612472575061242a6141ea565b806124355750612474565b806040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161246991906136bf565b60405180910390fd5b505b6040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016124a6906136e1565b60405180910390fd5b63f23a6e6160e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614612536576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161252d90613701565b60405180910390fd5b505b505050505050565b61255f8473ffffffffffffffffffffffffffffffffffffffff16612727565b1561271f578373ffffffffffffffffffffffffffffffffffffffff1663bc197c8187878686866040518663ffffffff1660e01b81526004016125a5959493929190613565565b602060405180830381600087803b1580156125bf57600080fd5b505af19250505080156125f057506040513d601f19601f820116820180604052508101906125ed9190612f82565b60015b612696576125fc613cba565b806308c379a0141561265957506126116141ea565b8061261c575061265b565b806040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161265091906136bf565b60405180910390fd5b505b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161268d906136e1565b60405180910390fd5b63bc197c8160e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19161461271d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161271490613701565b60405180910390fd5b505b505050505050565b6000808273ffffffffffffffffffffffffffffffffffffffff163b119050919050565b82805461275690613b81565b90600052602060002090601f01602090048101928261277857600085556127bf565b82601f1061279157805160ff19168380011785556127bf565b828001600101855582156127bf579182015b828111156127be5782518255916020019190600101906127a3565b5b5090506127cc91906127d0565b5090565b5b808211156127e95760008160009055506001016127d1565b5090565b60006128006127fb8461394a565b613925565b9050808382526020820190508285602086028201111561281f57600080fd5b60005b8581101561284f57816128358882612941565b845260208401935060208301925050600181019050612822565b5050509392505050565b600061286c61286784613976565b613925565b9050808382526020820190508285602086028201111561288b57600080fd5b60005b858110156128bb57816128a18882612b1b565b84526020840193506020830192505060018101905061288e565b5050509392505050565b60006128d86128d3846139a2565b613925565b9050828152602081018484840111156128f057600080fd5b6128fb848285613b3f565b509392505050565b6000612916612911846139d3565b613925565b90508281526020810184848401111561292e57600080fd5b612939848285613b3f565b509392505050565b60008135905061295081614280565b92915050565b60008083601f84011261296857600080fd5b8235905067ffffffffffffffff81111561298157600080fd5b60208301915083602082028301111561299957600080fd5b9250929050565b600082601f8301126129b157600080fd5b81356129c18482602086016127ed565b91505092915050565b60008083601f8401126129dc57600080fd5b8235905067ffffffffffffffff8111156129f557600080fd5b602083019150836020820283011115612a0d57600080fd5b9250929050565b600082601f830112612a2557600080fd5b8135612a35848260208601612859565b91505092915050565b600081359050612a4d81614297565b92915050565b600081359050612a62816142ae565b92915050565b600081519050612a77816142ae565b92915050565b60008083601f840112612a8f57600080fd5b8235905067ffffffffffffffff811115612aa857600080fd5b602083019150836001820283011115612ac057600080fd5b9250929050565b600082601f830112612ad857600080fd5b8135612ae88482602086016128c5565b91505092915050565b600082601f830112612b0257600080fd5b8135612b12848260208601612903565b91505092915050565b600081359050612b2a816142c5565b92915050565b600060208284031215612b4257600080fd5b6000612b5084828501612941565b91505092915050565b60008060408385031215612b6c57600080fd5b6000612b7a85828601612941565b9250506020612b8b85828601612941565b9150509250929050565b600080600080600060a08688031215612bad57600080fd5b6000612bbb88828901612941565b9550506020612bcc88828901612941565b945050604086013567ffffffffffffffff811115612be957600080fd5b612bf588828901612a14565b935050606086013567ffffffffffffffff811115612c1257600080fd5b612c1e88828901612a14565b925050608086013567ffffffffffffffff811115612c3b57600080fd5b612c4788828901612ac7565b9150509295509295909350565b600080600080600060a08688031215612c6c57600080fd5b6000612c7a88828901612941565b9550506020612c8b88828901612941565b9450506040612c9c88828901612b1b565b9350506060612cad88828901612b1b565b925050608086013567ffffffffffffffff811115612cca57600080fd5b612cd688828901612ac7565b9150509295509295909350565b600080600060608486031215612cf857600080fd5b6000612d0686828701612941565b935050602084013567ffffffffffffffff811115612d2357600080fd5b612d2f86828701612a14565b925050604084013567ffffffffffffffff811115612d4c57600080fd5b612d5886828701612a14565b9150509250925092565b60008060408385031215612d7557600080fd5b6000612d8385828601612941565b9250506020612d9485828601612a3e565b9150509250929050565b60008060008060608587031215612db457600080fd5b6000612dc287828801612941565b9450506020612dd387828801612a3e565b935050604085013567ffffffffffffffff811115612df057600080fd5b612dfc87828801612a7d565b925092505092959194509250565b60008060408385031215612e1d57600080fd5b6000612e2b85828601612941565b9250506020612e3c85828601612b1b565b9150509250929050565b600080600060608486031215612e5b57600080fd5b6000612e6986828701612941565b9350506020612e7a86828701612b1b565b9250506040612e8b86828701612b1b565b9150509250925092565b60008060408385031215612ea857600080fd5b600083013567ffffffffffffffff811115612ec257600080fd5b612ece858286016129a0565b925050602083013567ffffffffffffffff811115612eeb57600080fd5b612ef785828601612a14565b9150509250929050565b600080600060408486031215612f1657600080fd5b6000612f2486828701612a3e565b935050602084013567ffffffffffffffff811115612f4157600080fd5b612f4d86828701612a7d565b92509250509250925092565b600060208284031215612f6b57600080fd5b6000612f7984828501612a53565b91505092915050565b600060208284031215612f9457600080fd5b6000612fa284828501612a68565b91505092915050565b600060208284031215612fbd57600080fd5b6000612fcb84828501612b1b565b91505092915050565b60008060008060008060006080888a031215612fef57600080fd5b6000612ffd8a828b01612b1b565b975050602088013567ffffffffffffffff81111561301a57600080fd5b6130268a828b01612956565b9650965050604088013567ffffffffffffffff81111561304557600080fd5b6130518a828b016129ca565b9450945050606088013567ffffffffffffffff81111561307057600080fd5b61307c8a828b01612a7d565b925092505092959891949750929550565b6000806000806000606086880312156130a557600080fd5b60006130b388828901612b1b565b955050602086013567ffffffffffffffff8111156130d057600080fd5b6130dc88828901612956565b9450945050604086013567ffffffffffffffff8111156130fb57600080fd5b61310788828901612a7d565b92509250509295509295909350565b6000806000806000806080878903121561312f57600080fd5b600061313d89828a01612b1b565b965050602087013567ffffffffffffffff81111561315a57600080fd5b61316689828a01612956565b9550955050604087013567ffffffffffffffff81111561318557600080fd5b61319189828a01612a7d565b9350935050606087013567ffffffffffffffff8111156131b057600080fd5b6131bc89828a01612af1565b9150509295509295509295565b60006131d5838361352c565b60208301905092915050565b6131ea81613acb565b82525050565b60006131fb82613a14565b6132058185613a42565b935061321083613a04565b8060005b8381101561324157815161322888826131c9565b975061323383613a35565b925050600181019050613214565b5085935050505092915050565b61325781613add565b82525050565b60006132698385613a53565b9350613276838584613b3f565b61327f83613cdc565b840190509392505050565b600061329582613a1f565b61329f8185613a53565b93506132af818560208601613b4e565b6132b881613cdc565b840191505092915050565b60006132ce82613a2a565b6132d88185613a64565b93506132e8818560208601613b4e565b6132f181613cdc565b840191505092915050565b6000613309603483613a64565b915061331482613cfa565b604082019050919050565b600061332c602883613a64565b915061333782613d49565b604082019050919050565b600061334f602b83613a64565b915061335a82613d98565b604082019050919050565b6000613372602483613a64565b915061337d82613de7565b604082019050919050565b6000613395602983613a64565b91506133a082613e36565b604082019050919050565b60006133b8603f83613a64565b91506133c382613e85565b604082019050919050565b60006133db603b83613a64565b91506133e682613ed4565b604082019050919050565b60006133fe602583613a64565b915061340982613f23565b604082019050919050565b6000613421603283613a64565b915061342c82613f72565b604082019050919050565b6000613444603483613a64565b915061344f82613fc1565b604082019050919050565b6000613467602383613a64565b915061347282614010565b604082019050919050565b600061348a602a83613a64565b91506134958261405f565b604082019050919050565b60006134ad602983613a64565b91506134b8826140ae565b604082019050919050565b60006134d0602983613a64565b91506134db826140fd565b604082019050919050565b60006134f3602883613a64565b91506134fe8261414c565b604082019050919050565b6000613516602183613a64565b91506135218261419b565b604082019050919050565b61353581613b35565b82525050565b61354481613b35565b82525050565b600060208201905061355f60008301846131e1565b92915050565b600060a08201905061357a60008301886131e1565b61358760208301876131e1565b818103604083015261359981866131f0565b905081810360608301526135ad81856131f0565b905081810360808301526135c1818461328a565b90509695505050505050565b600060a0820190506135e260008301886131e1565b6135ef60208301876131e1565b6135fc604083018661353b565b613609606083018561353b565b818103608083015261361b818461328a565b90509695505050505050565b6000602082019050818103600083015261364181846131f0565b905092915050565b6000604082019050818103600083015261366381856131f0565b9050818103602083015261367781846131f0565b90509392505050565b6000602082019050613695600083018461324e565b92915050565b600060208201905081810360008301526136b681848661325d565b90509392505050565b600060208201905081810360008301526136d981846132c3565b905092915050565b600060208201905081810360008301526136fa816132fc565b9050919050565b6000602082019050818103600083015261371a8161331f565b9050919050565b6000602082019050818103600083015261373a81613342565b9050919050565b6000602082019050818103600083015261375a81613365565b9050919050565b6000602082019050818103600083015261377a81613388565b9050919050565b6000602082019050818103600083015261379a816133ab565b9050919050565b600060208201905081810360008301526137ba816133ce565b9050919050565b600060208201905081810360008301526137da816133f1565b9050919050565b600060208201905081810360008301526137fa81613414565b9050919050565b6000602082019050818103600083015261381a81613437565b9050919050565b6000602082019050818103600083015261383a8161345a565b9050919050565b6000602082019050818103600083015261385a8161347d565b9050919050565b6000602082019050818103600083015261387a816134a0565b9050919050565b6000602082019050818103600083015261389a816134c3565b9050919050565b600060208201905081810360008301526138ba816134e6565b9050919050565b600060208201905081810360008301526138da81613509565b9050919050565b60006020820190506138f6600083018461353b565b92915050565b6000604082019050613911600083018561353b565b61391e602083018461353b565b9392505050565b600061392f613940565b905061393b8282613bb3565b919050565b6000604051905090565b600067ffffffffffffffff82111561396557613964613c8b565b5b602082029050602081019050919050565b600067ffffffffffffffff82111561399157613990613c8b565b5b602082029050602081019050919050565b600067ffffffffffffffff8211156139bd576139bc613c8b565b5b6139c682613cdc565b9050602081019050919050565b600067ffffffffffffffff8211156139ee576139ed613c8b565b5b6139f782613cdc565b9050602081019050919050565b6000819050602082019050919050565b600081519050919050565b600081519050919050565b600081519050919050565b6000602082019050919050565b600082825260208201905092915050565b600082825260208201905092915050565b600082825260208201905092915050565b6000613a8082613b35565b9150613a8b83613b35565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115613ac057613abf613c2d565b5b828201905092915050565b6000613ad682613b15565b9050919050565b60008115159050919050565b60007fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b82818337600083830152505050565b60005b83811015613b6c578082015181840152602081019050613b51565b83811115613b7b576000848401525b50505050565b60006002820490506001821680613b9957607f821691505b60208210811415613bad57613bac613c5c565b5b50919050565b613bbc82613cdc565b810181811067ffffffffffffffff82111715613bdb57613bda613c8b565b5b80604052505050565b6000613bef82613b35565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff821415613c2257613c21613c2d565b5b600182019050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600060033d1115613cd95760046000803e613cd6600051613ced565b90505b90565b6000601f19601f8301169050919050565b60008160e01c9050919050565b7f455243313135353a207472616e7366657220746f206e6f6e204552433131353560008201527f526563656976657220696d706c656d656e746572000000000000000000000000602082015250565b7f455243313135353a204552433131353552656365697665722072656a6563746560008201527f6420746f6b656e73000000000000000000000000000000000000000000000000602082015250565b7f455243313135353a2062616c616e636520717565727920666f7220746865207a60008201527f65726f2061646472657373000000000000000000000000000000000000000000602082015250565b7f455243313135353a206275726e20616d6f756e7420657863656564732062616c60008201527f616e636500000000000000000000000000000000000000000000000000000000602082015250565b7f455243313135353a2063616c6c6572206973206e6f74206f776e6572206e6f7260008201527f20617070726f7665640000000000000000000000000000000000000000000000602082015250565b7f455243313135354d6978656446756e6769626c653a20696420646f6573206e6f60008201527f7420726570726573656e742061206e6f6e2d66756e6769626c65207479706500602082015250565b7f455243313135354d6978656446756e6769626c653a20696420646f6573206e6f60008201527f7420726570726573656e7420612066756e6769626c6520747970650000000000602082015250565b7f455243313135353a207472616e7366657220746f20746865207a65726f20616460008201527f6472657373000000000000000000000000000000000000000000000000000000602082015250565b7f455243313135353a207472616e736665722063616c6c6572206973206e6f742060008201527f6f776e6572206e6f7220617070726f7665640000000000000000000000000000602082015250565b7f455243313135354d6978656446756e6769626c653a20746f20616e6420616d6f60008201527f756e7473206c656e677468206d69736d61746368000000000000000000000000602082015250565b7f455243313135353a206275726e2066726f6d20746865207a65726f206164647260008201527f6573730000000000000000000000000000000000000000000000000000000000602082015250565b7f455243313135353a20696e73756666696369656e742062616c616e636520666f60008201527f72207472616e7366657200000000000000000000000000000000000000000000602082015250565b7f455243313135353a2073657474696e6720617070726f76616c2073746174757360008201527f20666f722073656c660000000000000000000000000000000000000000000000602082015250565b7f455243313135353a206163636f756e747320616e6420696473206c656e67746860008201527f206d69736d617463680000000000000000000000000000000000000000000000602082015250565b7f455243313135353a2069647320616e6420616d6f756e7473206c656e6774682060008201527f6d69736d61746368000000000000000000000000000000000000000000000000602082015250565b7f455243313135353a206d696e7420746f20746865207a65726f2061646472657360008201527f7300000000000000000000000000000000000000000000000000000000000000602082015250565b600060443d10156141fa5761427d565b614202613940565b60043d036004823e80513d602482011167ffffffffffffffff8211171561422a57505061427d565b808201805167ffffffffffffffff811115614248575050505061427d565b80602083010160043d03850181111561426557505050505061427d565b61427482602001850186613bb3565b82955050505050505b90565b61428981613acb565b811461429457600080fd5b50565b6142a081613add565b81146142ab57600080fd5b50565b6142b781613ae9565b81146142c257600080fd5b50565b6142ce81613b35565b81146142d957600080fd5b5056fea2646970667358221220294807453883ec73af4b9e8ca1836696c00f88042e85782bf38ba686bd810b9364736f6c63430008040033",
+  "linkReferences": {},
+  "deployedLinkReferences": {}
+}
diff --git a/test/data/contracts/erc20/ERC20OpenZeppelin.json b/test/data/contracts/erc20/ERC20OpenZeppelin.json
new file mode 100644
index 000000000..7e3cef66c
--- /dev/null
+++ b/test/data/contracts/erc20/ERC20OpenZeppelin.json
@@ -0,0 +1,387 @@
+{
+  "_format": "hh-sol-artifact-1",
+  "contractName": "MyToken",
+  "sourceName": "contracts/MyToken.sol",
+  "abi": [
+    {
+      "inputs": [],
+      "stateMutability": "nonpayable",
+      "type": "constructor"
+    },
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "owner",
+          "type": "address"
+        },
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "spender",
+          "type": "address"
+        },
+        {
+          "indexed": false,
+          "internalType": "uint256",
+          "name": "value",
+          "type": "uint256"
+        }
+      ],
+      "name": "Approval",
+      "type": "event"
+    },
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "previousOwner",
+          "type": "address"
+        },
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "newOwner",
+          "type": "address"
+        }
+      ],
+      "name": "OwnershipTransferred",
+      "type": "event"
+    },
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "from",
+          "type": "address"
+        },
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "to",
+          "type": "address"
+        },
+        {
+          "indexed": false,
+          "internalType": "uint256",
+          "name": "value",
+          "type": "uint256"
+        }
+      ],
+      "name": "Transfer",
+      "type": "event"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "owner",
+          "type": "address"
+        },
+        {
+          "internalType": "address",
+          "name": "spender",
+          "type": "address"
+        }
+      ],
+      "name": "allowance",
+      "outputs": [
+        {
+          "internalType": "uint256",
+          "name": "",
+          "type": "uint256"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "spender",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "amount",
+          "type": "uint256"
+        }
+      ],
+      "name": "approve",
+      "outputs": [
+        {
+          "internalType": "bool",
+          "name": "",
+          "type": "bool"
+        }
+      ],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "account",
+          "type": "address"
+        }
+      ],
+      "name": "balanceOf",
+      "outputs": [
+        {
+          "internalType": "uint256",
+          "name": "",
+          "type": "uint256"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "uint256",
+          "name": "amount",
+          "type": "uint256"
+        }
+      ],
+      "name": "burn",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "account",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "amount",
+          "type": "uint256"
+        }
+      ],
+      "name": "burnFrom",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [],
+      "name": "decimals",
+      "outputs": [
+        {
+          "internalType": "uint8",
+          "name": "",
+          "type": "uint8"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "spender",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "subtractedValue",
+          "type": "uint256"
+        }
+      ],
+      "name": "decreaseAllowance",
+      "outputs": [
+        {
+          "internalType": "bool",
+          "name": "",
+          "type": "bool"
+        }
+      ],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "spender",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "addedValue",
+          "type": "uint256"
+        }
+      ],
+      "name": "increaseAllowance",
+      "outputs": [
+        {
+          "internalType": "bool",
+          "name": "",
+          "type": "bool"
+        }
+      ],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "to",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "amount",
+          "type": "uint256"
+        }
+      ],
+      "name": "mint",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [],
+      "name": "name",
+      "outputs": [
+        {
+          "internalType": "string",
+          "name": "",
+          "type": "string"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [],
+      "name": "owner",
+      "outputs": [
+        {
+          "internalType": "address",
+          "name": "",
+          "type": "address"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [],
+      "name": "renounceOwnership",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [],
+      "name": "symbol",
+      "outputs": [
+        {
+          "internalType": "string",
+          "name": "",
+          "type": "string"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [],
+      "name": "totalSupply",
+      "outputs": [
+        {
+          "internalType": "uint256",
+          "name": "",
+          "type": "uint256"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "to",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "amount",
+          "type": "uint256"
+        }
+      ],
+      "name": "transfer",
+      "outputs": [
+        {
+          "internalType": "bool",
+          "name": "",
+          "type": "bool"
+        }
+      ],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "from",
+          "type": "address"
+        },
+        {
+          "internalType": "address",
+          "name": "to",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "amount",
+          "type": "uint256"
+        }
+      ],
+      "name": "transferFrom",
+      "outputs": [
+        {
+          "internalType": "bool",
+          "name": "",
+          "type": "bool"
+        }
+      ],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "newOwner",
+          "type": "address"
+        }
+      ],
+      "name": "transferOwnership",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    }
+  ],
+  "bytecode": "0x60806040523480156200001157600080fd5b506040518060400160405280600781526020016626bcaa37b5b2b760c91b815250604051806040016040528060038152602001624d544b60e81b815250816003908051906020019062000066929190620000f5565b5080516200007c906004906020840190620000f5565b50505062000099620000936200009f60201b60201c565b620000a3565b620001d8565b3390565b600580546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b82805462000103906200019b565b90600052602060002090601f01602090048101928262000127576000855562000172565b82601f106200014257805160ff191683800117855562000172565b8280016001018555821562000172579182015b828111156200017257825182559160200191906001019062000155565b506200018092915062000184565b5090565b5b8082111562000180576000815560010162000185565b600181811c90821680620001b057607f821691505b60208210811415620001d257634e487b7160e01b600052602260045260246000fd5b50919050565b610ca180620001e86000396000f3fe608060405234801561001057600080fd5b506004361061010b5760003560e01c806370a08231116100a257806395d89b411161007157806395d89b411461021f578063a457c2d714610227578063a9059cbb1461023a578063dd62ed3e1461024d578063f2fde38b1461026057600080fd5b806370a08231146101c0578063715018a6146101e957806379cc6790146101f15780638da5cb5b1461020457600080fd5b8063313ce567116100de578063313ce56714610176578063395093511461018557806340c10f191461019857806342966c68146101ad57600080fd5b806306fdde0314610110578063095ea7b31461012e57806318160ddd1461015157806323b872dd14610163575b600080fd5b610118610273565b6040516101259190610ac5565b60405180910390f35b61014161013c366004610b36565b610305565b6040519015158152602001610125565b6002545b604051908152602001610125565b610141610171366004610b60565b61031d565b60405160128152602001610125565b610141610193366004610b36565b610341565b6101ab6101a6366004610b36565b610363565b005b6101ab6101bb366004610b9c565b610379565b6101556101ce366004610bb5565b6001600160a01b031660009081526020819052604090205490565b6101ab610386565b6101ab6101ff366004610b36565b61039a565b6005546040516001600160a01b039091168152602001610125565b6101186103af565b610141610235366004610b36565b6103be565b610141610248366004610b36565b61043e565b61015561025b366004610bd7565b61044c565b6101ab61026e366004610bb5565b610477565b60606003805461028290610c0a565b80601f01602080910402602001604051908101604052809291908181526020018280546102ae90610c0a565b80156102fb5780601f106102d0576101008083540402835291602001916102fb565b820191906000526020600020905b8154815290600101906020018083116102de57829003601f168201915b5050505050905090565b6000336103138185856104ed565b5060019392505050565b60003361032b858285610612565b61033685858561068c565b506001949350505050565b600033610313818585610354838361044c565b61035e9190610c45565b6104ed565b61036b610830565b610375828261088a565b5050565b6103833382610949565b50565b61038e610830565b6103986000610a73565b565b6103a5823383610612565b6103758282610949565b60606004805461028290610c0a565b600033816103cc828661044c565b9050838110156104315760405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b60648201526084015b60405180910390fd5b61033682868684036104ed565b60003361031381858561068c565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b61047f610830565b6001600160a01b0381166104e45760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b6064820152608401610428565b61038381610a73565b6001600160a01b03831661054f5760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608401610428565b6001600160a01b0382166105b05760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608401610428565b6001600160a01b0383811660008181526001602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591015b60405180910390a3505050565b600061061e848461044c565b9050600019811461068657818110156106795760405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606401610428565b61068684848484036104ed565b50505050565b6001600160a01b0383166106f05760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b6064820152608401610428565b6001600160a01b0382166107525760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b6064820152608401610428565b6001600160a01b038316600090815260208190526040902054818110156107ca5760405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b6064820152608401610428565b6001600160a01b03848116600081815260208181526040808320878703905593871680835291849020805487019055925185815290927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a3610686565b6005546001600160a01b031633146103985760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610428565b6001600160a01b0382166108e05760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152606401610428565b80600260008282546108f29190610c45565b90915550506001600160a01b038216600081815260208181526040808320805486019055518481527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a35050565b6001600160a01b0382166109a95760405162461bcd60e51b815260206004820152602160248201527f45524332303a206275726e2066726f6d20746865207a65726f206164647265736044820152607360f81b6064820152608401610428565b6001600160a01b03821660009081526020819052604090205481811015610a1d5760405162461bcd60e51b815260206004820152602260248201527f45524332303a206275726e20616d6f756e7420657863656564732062616c616e604482015261636560f01b6064820152608401610428565b6001600160a01b0383166000818152602081815260408083208686039055600280548790039055518581529192917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9101610605565b600580546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b600060208083528351808285015260005b81811015610af257858101830151858201604001528201610ad6565b81811115610b04576000604083870101525b50601f01601f1916929092016040019392505050565b80356001600160a01b0381168114610b3157600080fd5b919050565b60008060408385031215610b4957600080fd5b610b5283610b1a565b946020939093013593505050565b600080600060608486031215610b7557600080fd5b610b7e84610b1a565b9250610b8c60208501610b1a565b9150604084013590509250925092565b600060208284031215610bae57600080fd5b5035919050565b600060208284031215610bc757600080fd5b610bd082610b1a565b9392505050565b60008060408385031215610bea57600080fd5b610bf383610b1a565b9150610c0160208401610b1a565b90509250929050565b600181811c90821680610c1e57607f821691505b60208210811415610c3f57634e487b7160e01b600052602260045260246000fd5b50919050565b60008219821115610c6657634e487b7160e01b600052601160045260246000fd5b50019056fea2646970667358221220e098e84337ed515fa28fa493e8123cac9696f689e1707acbf904514a57749b2f64736f6c63430008090033",
+  "deployedBytecode": "0x608060405234801561001057600080fd5b506004361061010b5760003560e01c806370a08231116100a257806395d89b411161007157806395d89b411461021f578063a457c2d714610227578063a9059cbb1461023a578063dd62ed3e1461024d578063f2fde38b1461026057600080fd5b806370a08231146101c0578063715018a6146101e957806379cc6790146101f15780638da5cb5b1461020457600080fd5b8063313ce567116100de578063313ce56714610176578063395093511461018557806340c10f191461019857806342966c68146101ad57600080fd5b806306fdde0314610110578063095ea7b31461012e57806318160ddd1461015157806323b872dd14610163575b600080fd5b610118610273565b6040516101259190610ac5565b60405180910390f35b61014161013c366004610b36565b610305565b6040519015158152602001610125565b6002545b604051908152602001610125565b610141610171366004610b60565b61031d565b60405160128152602001610125565b610141610193366004610b36565b610341565b6101ab6101a6366004610b36565b610363565b005b6101ab6101bb366004610b9c565b610379565b6101556101ce366004610bb5565b6001600160a01b031660009081526020819052604090205490565b6101ab610386565b6101ab6101ff366004610b36565b61039a565b6005546040516001600160a01b039091168152602001610125565b6101186103af565b610141610235366004610b36565b6103be565b610141610248366004610b36565b61043e565b61015561025b366004610bd7565b61044c565b6101ab61026e366004610bb5565b610477565b60606003805461028290610c0a565b80601f01602080910402602001604051908101604052809291908181526020018280546102ae90610c0a565b80156102fb5780601f106102d0576101008083540402835291602001916102fb565b820191906000526020600020905b8154815290600101906020018083116102de57829003601f168201915b5050505050905090565b6000336103138185856104ed565b5060019392505050565b60003361032b858285610612565b61033685858561068c565b506001949350505050565b600033610313818585610354838361044c565b61035e9190610c45565b6104ed565b61036b610830565b610375828261088a565b5050565b6103833382610949565b50565b61038e610830565b6103986000610a73565b565b6103a5823383610612565b6103758282610949565b60606004805461028290610c0a565b600033816103cc828661044c565b9050838110156104315760405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b60648201526084015b60405180910390fd5b61033682868684036104ed565b60003361031381858561068c565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b61047f610830565b6001600160a01b0381166104e45760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b6064820152608401610428565b61038381610a73565b6001600160a01b03831661054f5760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608401610428565b6001600160a01b0382166105b05760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608401610428565b6001600160a01b0383811660008181526001602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591015b60405180910390a3505050565b600061061e848461044c565b9050600019811461068657818110156106795760405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606401610428565b61068684848484036104ed565b50505050565b6001600160a01b0383166106f05760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b6064820152608401610428565b6001600160a01b0382166107525760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b6064820152608401610428565b6001600160a01b038316600090815260208190526040902054818110156107ca5760405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b6064820152608401610428565b6001600160a01b03848116600081815260208181526040808320878703905593871680835291849020805487019055925185815290927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a3610686565b6005546001600160a01b031633146103985760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610428565b6001600160a01b0382166108e05760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152606401610428565b80600260008282546108f29190610c45565b90915550506001600160a01b038216600081815260208181526040808320805486019055518481527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a35050565b6001600160a01b0382166109a95760405162461bcd60e51b815260206004820152602160248201527f45524332303a206275726e2066726f6d20746865207a65726f206164647265736044820152607360f81b6064820152608401610428565b6001600160a01b03821660009081526020819052604090205481811015610a1d5760405162461bcd60e51b815260206004820152602260248201527f45524332303a206275726e20616d6f756e7420657863656564732062616c616e604482015261636560f01b6064820152608401610428565b6001600160a01b0383166000818152602081815260408083208686039055600280548790039055518581529192917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9101610605565b600580546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b600060208083528351808285015260005b81811015610af257858101830151858201604001528201610ad6565b81811115610b04576000604083870101525b50601f01601f1916929092016040019392505050565b80356001600160a01b0381168114610b3157600080fd5b919050565b60008060408385031215610b4957600080fd5b610b5283610b1a565b946020939093013593505050565b600080600060608486031215610b7557600080fd5b610b7e84610b1a565b9250610b8c60208501610b1a565b9150604084013590509250925092565b600060208284031215610bae57600080fd5b5035919050565b600060208284031215610bc757600080fd5b610bd082610b1a565b9392505050565b60008060408385031215610bea57600080fd5b610bf383610b1a565b9150610c0160208401610b1a565b90509250929050565b600181811c90821680610c1e57607f821691505b60208210811415610c3f57634e487b7160e01b600052602260045260246000fd5b50919050565b60008219821115610c6657634e487b7160e01b600052601160045260246000fd5b50019056fea2646970667358221220e098e84337ed515fa28fa493e8123cac9696f689e1707acbf904514a57749b2f64736f6c63430008090033",
+  "linkReferences": {},
+  "deployedLinkReferences": {}
+}
diff --git a/test/e2e/client/restclient.go b/test/e2e/client/restclient.go
index 35231b573..fc9eed507 100644
--- a/test/e2e/client/restclient.go
+++ b/test/e2e/client/restclient.go
@@ -1,4 +1,4 @@
-// Copyright © 2022 Kaleido, Inc.
+// Copyright © 2023 Kaleido, Inc.
 //
 // SPDX-License-Identifier: Apache-2.0
 //
@@ -64,6 +64,7 @@ var (
 	urlContractInvoke    = "/contracts/invoke"
 	urlContractQuery     = "/contracts/query"
 	urlContractInterface = "/contracts/interfaces"
+	urlContractGenerate  = "/contracts/interfaces/generate"
 	urlContractListeners = "/contracts/listeners"
 	urlContractAPI       = "/apis"
 	urlBlockchainEvents  = "/blockchainevents"
@@ -576,6 +577,16 @@ func (client *FireFlyClient) GetTokenPools(t *testing.T, startTime time.Time) (p
 	return pools
 }
 
+func (client *FireFlyClient) GetTokenPool(t *testing.T, poolID *fftypes.UUID) (pool *core.TokenPool) {
+	path := client.namespaced(urlTokenPools + "/" + poolID.String())
+	resp, err := client.Client.R().
+		SetResult(&pool).
+		Get(path)
+	require.NoError(t, err)
+	require.Equal(t, 200, resp.StatusCode(), "GET %s [%d]: %s", path, resp.StatusCode(), resp.String())
+	return pool
+}
+
 func (client *FireFlyClient) MintTokens(t *testing.T, mint *core.TokenTransferInput, confirm bool, expectedStatus ...int) *core.TokenTransfer {
 	var transferOut core.TokenTransfer
 	path := client.namespaced(urlTokenMint)
@@ -711,7 +722,7 @@ func (client *FireFlyClient) GetTokenBalance(t *testing.T, poolID *fftypes.UUID,
 		Get(path)
 	require.NoError(t, err)
 	require.Equal(t, 200, resp.StatusCode(), "GET %s [%d]: %s", path, resp.StatusCode(), resp.String())
-	require.Equal(t, len(accounts), 1)
+	require.Equal(t, 1, len(accounts))
 	return accounts[0]
 }
 
@@ -813,6 +824,18 @@ func (client *FireFlyClient) QueryContractMethod(t *testing.T, req *core.Contrac
 	return res, err
 }
 
+func (client *FireFlyClient) GenerateFFIFromABI(t *testing.T, req *fftypes.FFIGenerationRequest) *fftypes.FFI {
+	var res fftypes.FFI
+	path := client.namespaced(urlContractGenerate)
+	resp, err := client.Client.R().
+		SetBody(req).
+		SetResult(&res).
+		Post(path)
+	require.NoError(t, err)
+	require.Equal(t, 200, resp.StatusCode(), "POST %s [%d]: %s", path, resp.StatusCode(), resp.String())
+	return &res
+}
+
 func (client *FireFlyClient) CreateFFI(t *testing.T, ffi *fftypes.FFI) (interface{}, error) {
 	var res interface{}
 	path := client.namespaced(urlContractInterface)
diff --git a/test/e2e/multiparty/contract_migration.go b/test/e2e/multiparty/contract_migration.go
index 63e8cc16c..f3db4c8c4 100644
--- a/test/e2e/multiparty/contract_migration.go
+++ b/test/e2e/multiparty/contract_migration.go
@@ -1,4 +1,4 @@
-// Copyright © 2022 Kaleido, Inc.
+// Copyright © 2023 Kaleido, Inc.
 //
 // SPDX-License-Identifier: Apache-2.0
 //
@@ -34,16 +34,13 @@ type ContractMigrationTestSuite struct {
 	testState *testState
 }
 
-func (suite *ContractMigrationTestSuite) SetupSuite() {
-	suite.testState = beforeE2ETest(suite.T())
-}
-
 func (suite *ContractMigrationTestSuite) BeforeTest(suiteName, testName string) {
 	suite.testState = beforeE2ETest(suite.T())
 }
 
 func (suite *ContractMigrationTestSuite) AfterTest(suiteName, testName string) {
 	e2e.VerifyAllOperationsSucceeded(suite.T(), []*client.FireFlyClient{suite.testState.client1, suite.testState.client2}, suite.testState.startTime)
+	suite.testState.done()
 }
 
 func (suite *ContractMigrationTestSuite) TestContractMigration() {
diff --git a/test/e2e/multiparty/contract_migration_v1.go b/test/e2e/multiparty/contract_migration_v1.go
index d93c308ae..007f6f202 100644
--- a/test/e2e/multiparty/contract_migration_v1.go
+++ b/test/e2e/multiparty/contract_migration_v1.go
@@ -1,4 +1,4 @@
-// Copyright © 2022 Kaleido, Inc.
+// Copyright © 2023 Kaleido, Inc.
 //
 // SPDX-License-Identifier: Apache-2.0
 //
@@ -25,16 +25,13 @@ type ContractMigrationV1TestSuite struct {
 	ContractMigrationTestSuite
 }
 
-func (suite *ContractMigrationV1TestSuite) SetupSuite() {
-	suite.testState = beforeE2ETest(suite.T())
-}
-
 func (suite *ContractMigrationV1TestSuite) BeforeTest(suiteName, testName string) {
 	suite.testState = beforeE2ETest(suite.T())
 }
 
 func (suite *ContractMigrationV1TestSuite) AfterTest(suiteName, testName string) {
 	e2e.VerifyAllOperationsSucceeded(suite.T(), []*client.FireFlyClient{suite.testState.client1, suite.testState.client2}, suite.testState.startTime)
+	suite.testState.done()
 }
 
 func (suite *ContractMigrationV1TestSuite) TestContractMigration() {
diff --git a/test/e2e/multiparty/ethereum_contracts.go b/test/e2e/multiparty/ethereum_contracts.go
index 329b125c5..967d65918 100644
--- a/test/e2e/multiparty/ethereum_contracts.go
+++ b/test/e2e/multiparty/ethereum_contracts.go
@@ -1,4 +1,4 @@
-// Copyright © 2022 Kaleido, Inc.
+// Copyright © 2023 Kaleido, Inc.
 //
 // SPDX-License-Identifier: Apache-2.0
 //
@@ -19,6 +19,8 @@ package multiparty
 import (
 	"encoding/json"
 	"fmt"
+	"io/ioutil"
+	"os"
 	"os/exec"
 	"testing"
 
@@ -134,6 +136,7 @@ func (suite *EthereumContractTestSuite) BeforeTest(suiteName, testName string) {
 
 func (suite *EthereumContractTestSuite) AfterTest(suiteName, testName string) {
 	e2e.VerifyAllOperationsSucceeded(suite.T(), []*client.FireFlyClient{suite.testState.client1, suite.testState.client2}, suite.testState.startTime)
+	suite.testState.done()
 }
 
 func (suite *EthereumContractTestSuite) TestDirectInvokeMethod() {
@@ -307,3 +310,16 @@ func (suite *EthereumContractTestSuite) TestContractAPIMethod() {
 
 	suite.testState.client1.DeleteContractListener(suite.T(), listener.ID)
 }
+
+func readContractJSON(t *testing.T, contract string) fftypes.JSONObject {
+	path := "../../data/contracts/" + contract
+	jsonFile, err := os.Open(path)
+	assert.NoError(t, err)
+	defer jsonFile.Close()
+	byteValue, err := ioutil.ReadAll(jsonFile)
+	assert.NoError(t, err)
+	var jsonValue fftypes.JSONObject
+	err = json.Unmarshal(byteValue, &jsonValue)
+	assert.NoError(t, err)
+	return jsonValue
+}
diff --git a/test/e2e/multiparty/ethereum_token_contract.go b/test/e2e/multiparty/ethereum_token_contract.go
new file mode 100644
index 000000000..14eaa525e
--- /dev/null
+++ b/test/e2e/multiparty/ethereum_token_contract.go
@@ -0,0 +1,470 @@
+// Copyright © 2023 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 multiparty
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/hyperledger/firefly-common/pkg/fftypes"
+	"github.com/hyperledger/firefly/pkg/core"
+	"github.com/hyperledger/firefly/test/e2e"
+	"github.com/hyperledger/firefly/test/e2e/client"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/suite"
+)
+
+var expectedERC20Methods = fftypes.JSONAnyPtr(`{
+	"approval": {
+		"format": "abi",
+		"methods": [
+		{
+			"type": "function",
+			"name": "approve",
+			"stateMutability": "nonpayable",
+			"inputs": [
+			{
+				"name": "spender",
+				"type": "address",
+				"internalType": "address"
+			},
+			{
+				"name": "amount",
+				"type": "uint256",
+				"internalType": "uint256"
+			}
+			],
+			"outputs": [
+			{
+				"name": "",
+				"type": "bool",
+				"internalType": "bool"
+			}
+			]
+		}
+		]
+	},
+	"burn": {
+		"format": "abi",
+		"methods": [
+		{
+			"type": "function",
+			"name": "burn",
+			"stateMutability": "nonpayable",
+			"inputs": [
+			{
+				"name": "amount",
+				"type": "uint256",
+				"internalType": "uint256"
+			}
+			],
+			"outputs": []
+		},
+		{
+			"type": "function",
+			"name": "burnFrom",
+			"stateMutability": "nonpayable",
+			"inputs": [
+			{
+				"name": "account",
+				"type": "address",
+				"internalType": "address"
+			},
+			{
+				"name": "amount",
+				"type": "uint256",
+				"internalType": "uint256"
+			}
+			],
+			"outputs": []
+		}
+		]
+	},
+	"mint": {
+		"format": "abi",
+		"methods": [
+		{
+			"type": "function",
+			"name": "mint",
+			"stateMutability": "nonpayable",
+			"inputs": [
+			{
+				"name": "to",
+				"type": "address",
+				"internalType": "address"
+			},
+			{
+				"name": "amount",
+				"type": "uint256",
+				"internalType": "uint256"
+			}
+			],
+			"outputs": []
+		}
+		]
+	},
+	"transfer": {
+		"format": "abi",
+		"methods": [
+		{
+			"type": "function",
+			"name": "transfer",
+			"stateMutability": "nonpayable",
+			"inputs": [
+			{
+				"name": "to",
+				"type": "address",
+				"internalType": "address"
+			},
+			{
+				"name": "amount",
+				"type": "uint256",
+				"internalType": "uint256"
+			}
+			],
+			"outputs": [
+			{
+				"name": "",
+				"type": "bool",
+				"internalType": "bool"
+			}
+			]
+		},
+		{
+			"type": "function",
+			"name": "transferFrom",
+			"stateMutability": "nonpayable",
+			"inputs": [
+			{
+				"name": "from",
+				"type": "address",
+				"internalType": "address"
+			},
+			{
+				"name": "to",
+				"type": "address",
+				"internalType": "address"
+			},
+			{
+				"name": "amount",
+				"type": "uint256",
+				"internalType": "uint256"
+			}
+			],
+			"outputs": [
+			{
+				"name": "",
+				"type": "bool",
+				"internalType": "bool"
+			}
+			]
+		}
+		]
+	}
+}`)
+
+var expectedERC1155Methods = fftypes.JSONAnyPtr(`{
+	"approval": {
+	  "format": "abi",
+	  "methods": [
+		{
+		  "type": "function",
+		  "name": "setApprovalForAllWithData",
+		  "stateMutability": "nonpayable",
+		  "inputs": [
+			{
+			  "name": "operator",
+			  "type": "address",
+			  "internalType": "address"
+			},
+			{
+			  "name": "approved",
+			  "type": "bool",
+			  "internalType": "bool"
+			},
+			{
+			  "name": "data",
+			  "type": "bytes",
+			  "internalType": "bytes"
+			}
+		  ],
+		  "outputs": []
+		},
+		{
+		  "type": "function",
+		  "name": "setApprovalForAll",
+		  "stateMutability": "nonpayable",
+		  "inputs": [
+			{
+			  "name": "operator",
+			  "type": "address",
+			  "internalType": "address"
+			},
+			{
+			  "name": "approved",
+			  "type": "bool",
+			  "internalType": "bool"
+			}
+		  ],
+		  "outputs": []
+		}
+	  ]
+	},
+	"burn": {
+	  "format": "abi",
+	  "methods": [
+		{
+		  "type": "function",
+		  "name": "burn",
+		  "stateMutability": "nonpayable",
+		  "inputs": [
+			{
+			  "name": "account",
+			  "type": "address",
+			  "internalType": "address"
+			},
+			{
+			  "name": "id",
+			  "type": "uint256",
+			  "internalType": "uint256"
+			},
+			{
+			  "name": "value",
+			  "type": "uint256",
+			  "internalType": "uint256"
+			}
+		  ],
+		  "outputs": []
+		}
+	  ]
+	},
+	"mint": {
+	  "format": "abi",
+	  "methods": [
+		{
+		  "type": "function",
+		  "name": "mintFungible",
+		  "stateMutability": "nonpayable",
+		  "inputs": [
+			{
+			  "name": "type_id",
+			  "type": "uint256",
+			  "internalType": "uint256"
+			},
+			{
+			  "name": "to",
+			  "type": "address[]",
+			  "internalType": "address[]"
+			},
+			{
+			  "name": "amounts",
+			  "type": "uint256[]",
+			  "internalType": "uint256[]"
+			},
+			{
+			  "name": "data",
+			  "type": "bytes",
+			  "internalType": "bytes"
+			}
+		  ],
+		  "outputs": []
+		},
+		{
+		  "type": "function",
+		  "name": "mintNonFungibleWithURI",
+		  "stateMutability": "nonpayable",
+		  "inputs": [
+			{
+			  "name": "type_id",
+			  "type": "uint256",
+			  "internalType": "uint256"
+			},
+			{
+			  "name": "to",
+			  "type": "address[]",
+			  "internalType": "address[]"
+			},
+			{
+			  "name": "data",
+			  "type": "bytes",
+			  "internalType": "bytes"
+			},
+			{
+			  "name": "_uri",
+			  "type": "string",
+			  "internalType": "string"
+			}
+		  ],
+		  "outputs": []
+		},
+		{
+		  "type": "function",
+		  "name": "mintNonFungible",
+		  "stateMutability": "nonpayable",
+		  "inputs": [
+			{
+			  "name": "type_id",
+			  "type": "uint256",
+			  "internalType": "uint256"
+			},
+			{
+			  "name": "to",
+			  "type": "address[]",
+			  "internalType": "address[]"
+			},
+			{
+			  "name": "data",
+			  "type": "bytes",
+			  "internalType": "bytes"
+			}
+		  ],
+		  "outputs": []
+		}
+	  ]
+	},
+	"transfer": {
+	  "format": "abi",
+	  "methods": [
+		{
+		  "type": "function",
+		  "name": "safeTransferFrom",
+		  "stateMutability": "nonpayable",
+		  "inputs": [
+			{
+			  "name": "from",
+			  "type": "address",
+			  "internalType": "address"
+			},
+			{
+			  "name": "to",
+			  "type": "address",
+			  "internalType": "address"
+			},
+			{
+			  "name": "id",
+			  "type": "uint256",
+			  "internalType": "uint256"
+			},
+			{
+			  "name": "amount",
+			  "type": "uint256",
+			  "internalType": "uint256"
+			},
+			{
+			  "name": "data",
+			  "type": "bytes",
+			  "internalType": "bytes"
+			}
+		  ],
+		  "outputs": []
+		}
+	  ]
+	}
+  }`)
+
+type EthereumTokenContractTestSuite struct {
+	suite.Suite
+	testState       *testState
+	contract        string
+	expectedMethods *fftypes.JSONAny
+}
+
+func (suite *EthereumTokenContractTestSuite) SetupSuite() {
+	stack := e2e.ReadStack(suite.T())
+	suite.contract = "erc20/ERC20OpenZeppelin.json"
+	suite.expectedMethods = expectedERC20Methods
+	if stack.TokenProviders[0] == "erc1155" {
+		suite.contract = "erc1155/ERC1155Sample.json"
+		suite.expectedMethods = expectedERC1155Methods
+	}
+}
+
+func (suite *EthereumTokenContractTestSuite) BeforeTest(suiteName, testName string) {
+	suite.testState = beforeE2ETest(suite.T())
+}
+
+func (suite *EthereumTokenContractTestSuite) AfterTest(suiteName, testName string) {
+	e2e.VerifyAllOperationsSucceeded(suite.T(), []*client.FireFlyClient{suite.testState.client1, suite.testState.client2}, suite.testState.startTime)
+	suite.testState.done()
+}
+
+func (suite *EthereumTokenContractTestSuite) TestTokensWithInterface() {
+	defer suite.testState.Done()
+
+	received1 := e2e.WsReader(suite.testState.ws1)
+
+	suite.T().Logf("contract: %s", suite.contract)
+	contractAddress := deployContract(suite.T(), suite.testState.stackName, suite.contract)
+	contractJSON := readContractJSON(suite.T(), suite.contract)
+
+	ffi := suite.testState.client1.GenerateFFIFromABI(suite.T(), &fftypes.FFIGenerationRequest{
+		Name:    "ERC20",
+		Version: contractVersion,
+		Input:   fftypes.JSONAnyPtr(`{"abi":` + contractJSON.GetObjectArray("abi").String() + `}`),
+	})
+	_, err := suite.testState.client1.CreateFFI(suite.T(), ffi)
+	assert.NoError(suite.T(), err)
+
+	poolName := fmt.Sprintf("pool_%s", e2e.RandomName(suite.T()))
+	pool := &core.TokenPool{
+		Name: poolName,
+		Type: core.TokenTypeFungible,
+		Config: fftypes.JSONObject{
+			"address": contractAddress,
+		},
+		Interface: &fftypes.FFIReference{
+			Name:    "ERC20",
+			Version: contractVersion,
+		},
+	}
+	poolResp := suite.testState.client1.CreateTokenPool(suite.T(), pool, false)
+	e2e.WaitForEvent(suite.T(), received1, core.EventTypePoolConfirmed, poolResp.ID)
+
+	poolResp = suite.testState.client1.GetTokenPool(suite.T(), poolResp.ID)
+	assert.Equal(suite.T(), core.TokenInterfaceFormatABI, poolResp.InterfaceFormat)
+	assert.Equal(suite.T(), suite.expectedMethods.JSONObject(), poolResp.Methods.JSONObject())
+
+	transfer := &core.TokenTransferInput{
+		TokenTransfer: core.TokenTransfer{Amount: *fftypes.NewFFBigInt(1)},
+		Pool:          poolName,
+	}
+	suite.testState.client1.MintTokens(suite.T(), transfer, false)
+	e2e.WaitForEvent(suite.T(), received1, core.EventTypeTransferConfirmed, nil)
+
+	transfers := suite.testState.client1.GetTokenTransfers(suite.T(), poolResp.ID)
+	assert.Len(suite.T(), transfers, 1)
+	blockchainEvent := suite.testState.client1.GetBlockchainEvent(suite.T(), transfers[0].BlockchainEvent.String())
+	assert.Equal(suite.T(), strings.ToLower(contractAddress), strings.ToLower(blockchainEvent.Info["address"].(string)))
+
+	e2e.ValidateAccountBalances(suite.T(), suite.testState.client1, poolResp.ID, "", map[string]int64{
+		suite.testState.org1key.Value: 1,
+	})
+
+	transfer = &core.TokenTransferInput{
+		TokenTransfer: core.TokenTransfer{Amount: *fftypes.NewFFBigInt(1)},
+		Pool:          poolName,
+	}
+	suite.testState.client1.BurnTokens(suite.T(), transfer, false)
+	e2e.WaitForEvent(suite.T(), received1, core.EventTypeTransferConfirmed, nil)
+
+	transfers = suite.testState.client1.GetTokenTransfers(suite.T(), poolResp.ID)
+	assert.Len(suite.T(), transfers, 2)
+
+	e2e.ValidateAccountBalances(suite.T(), suite.testState.client1, poolResp.ID, "", map[string]int64{
+		suite.testState.org1key.Value: 0,
+	})
+}
diff --git a/test/e2e/multiparty/ethereum_v1_migration.go b/test/e2e/multiparty/ethereum_v1_migration.go
deleted file mode 100644
index 2e628e763..000000000
--- a/test/e2e/multiparty/ethereum_v1_migration.go
+++ /dev/null
@@ -1,27 +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 multiparty
-
-import (
-	"testing"
-
-	"github.com/stretchr/testify/suite"
-)
-
-func TestEthereumMultipartyV1MigrationE2ESuite(t *testing.T) {
-	suite.Run(t, new(ContractMigrationV1TestSuite))
-}
diff --git a/test/e2e/multiparty/fabric_contracts.go b/test/e2e/multiparty/fabric_contracts.go
index 5677877c3..451d3714d 100644
--- a/test/e2e/multiparty/fabric_contracts.go
+++ b/test/e2e/multiparty/fabric_contracts.go
@@ -1,4 +1,4 @@
-// Copyright © 2022 Kaleido, Inc.
+// Copyright © 2023 Kaleido, Inc.
 //
 // SPDX-License-Identifier: Apache-2.0
 //
@@ -109,11 +109,10 @@ func (suite *FabricContractTestSuite) BeforeTest(suiteName, testName string) {
 
 func (suite *FabricContractTestSuite) AfterTest(suiteName, testName string) {
 	e2e.VerifyAllOperationsSucceeded(suite.T(), []*client.FireFlyClient{suite.testState.client1, suite.testState.client2}, suite.testState.startTime)
+	suite.testState.done()
 }
 
 func (suite *FabricContractTestSuite) TestE2EContractEvents() {
-	defer suite.testState.done()
-
 	received1 := e2e.WsReader(suite.testState.ws1)
 
 	sub := suite.testState.client1.CreateContractListener(suite.T(), assetCreatedEvent, &fftypes.JSONObject{
diff --git a/test/e2e/multiparty/identity.go b/test/e2e/multiparty/identity.go
index 9fa573e81..89c804bc7 100644
--- a/test/e2e/multiparty/identity.go
+++ b/test/e2e/multiparty/identity.go
@@ -1,4 +1,4 @@
-// Copyright © 2022 Kaleido, Inc.
+// Copyright © 2023 Kaleido, Inc.
 //
 // SPDX-License-Identifier: Apache-2.0
 //
@@ -42,11 +42,10 @@ func (suite *IdentityTestSuite) BeforeTest(suiteName, testName string) {
 
 func (suite *IdentityTestSuite) AfterTest(suiteName, testName string) {
 	e2e.VerifyAllOperationsSucceeded(suite.T(), []*client.FireFlyClient{suite.testState.client1, suite.testState.client2}, suite.testState.startTime)
+	suite.testState.done()
 }
 
 func (suite *IdentityTestSuite) TestCustomChildIdentityBroadcasts() {
-	defer suite.testState.done()
-
 	ctx := context.Background()
 	received1 := e2e.WsReader(suite.testState.ws1)
 	received2 := e2e.WsReader(suite.testState.ws2)
@@ -99,8 +98,6 @@ func (suite *IdentityTestSuite) TestCustomChildIdentityBroadcasts() {
 }
 
 func (suite *IdentityTestSuite) TestCustomChildIdentityPrivate() {
-	defer suite.testState.done()
-
 	received1 := e2e.WsReader(suite.testState.ws1)
 	received2 := e2e.WsReader(suite.testState.ws2)
 
@@ -143,8 +140,6 @@ func (suite *IdentityTestSuite) TestCustomChildIdentityPrivate() {
 }
 
 func (suite *IdentityTestSuite) TestInvalidIdentityAlreadyRegistered() {
-	defer suite.testState.done()
-
 	received1 := e2e.WsReader(suite.testState.ws1)
 
 	key := getUnregisteredAccount(suite, suite.testState.org1.Name)
diff --git a/test/e2e/multiparty/multi_tenancy.go b/test/e2e/multiparty/multi_tenancy.go
index 86722699c..36317e7a3 100644
--- a/test/e2e/multiparty/multi_tenancy.go
+++ b/test/e2e/multiparty/multi_tenancy.go
@@ -1,4 +1,4 @@
-// Copyright © 2022 Kaleido, Inc.
+// Copyright © 2023 Kaleido, Inc.
 //
 // SPDX-License-Identifier: Apache-2.0
 //
@@ -33,16 +33,13 @@ type NamespaceAliasSuite struct {
 	testState *testState
 }
 
-func (suite *NamespaceAliasSuite) SetupSuite() {
-	suite.testState = beforeE2ETest(suite.T())
-}
-
 func (suite *NamespaceAliasSuite) BeforeTest(suiteName, testName string) {
 	suite.testState = beforeE2ETest(suite.T())
 }
 
 func (suite *NamespaceAliasSuite) AfterTest(suiteName, testName string) {
 	e2e.VerifyAllOperationsSucceeded(suite.T(), []*client.FireFlyClient{suite.testState.client1, suite.testState.client2}, suite.testState.startTime)
+	suite.testState.done()
 }
 
 func newNamespace(networkNamespace, contractAddress string) (ns, org, node map[string]interface{}) {
diff --git a/test/e2e/multiparty/onchain_offchain.go b/test/e2e/multiparty/onchain_offchain.go
index 165f7efb1..afc9f6a57 100644
--- a/test/e2e/multiparty/onchain_offchain.go
+++ b/test/e2e/multiparty/onchain_offchain.go
@@ -1,4 +1,4 @@
-// Copyright © 2022 Kaleido, Inc.
+// Copyright © 2023 Kaleido, Inc.
 //
 // SPDX-License-Identifier: Apache-2.0
 //
@@ -48,11 +48,10 @@ func (suite *OnChainOffChainTestSuite) BeforeTest(suiteName, testName string) {
 
 func (suite *OnChainOffChainTestSuite) AfterTest(suiteName, testName string) {
 	e2e.VerifyAllOperationsSucceeded(suite.T(), []*client.FireFlyClient{suite.testState.client1, suite.testState.client2}, suite.testState.startTime)
+	suite.testState.done()
 }
 
 func (suite *OnChainOffChainTestSuite) TestE2EBroadcast() {
-	defer suite.testState.done()
-
 	received1 := e2e.WsReader(suite.testState.ws1)
 	received2 := e2e.WsReader(suite.testState.ws2)
 
@@ -100,8 +99,6 @@ func (suite *OnChainOffChainTestSuite) TestE2EBroadcast() {
 }
 
 func (suite *OnChainOffChainTestSuite) TestStrongDatatypesBroadcast() {
-	defer suite.testState.done()
-
 	received1 := e2e.WsReader(suite.testState.ws1)
 	received2 := e2e.WsReader(suite.testState.ws2)
 
@@ -149,8 +146,6 @@ func (suite *OnChainOffChainTestSuite) TestStrongDatatypesBroadcast() {
 }
 
 func (suite *OnChainOffChainTestSuite) TestStrongDatatypesPrivate() {
-	defer suite.testState.done()
-
 	received1 := e2e.WsReader(suite.testState.ws1)
 	received2 := e2e.WsReader(suite.testState.ws2)
 
@@ -203,9 +198,6 @@ func (suite *OnChainOffChainTestSuite) TestStrongDatatypesPrivate() {
 }
 
 func (suite *OnChainOffChainTestSuite) TestE2EPrivate() {
-
-	defer suite.testState.done()
-
 	received1 := e2e.WsReader(suite.testState.ws1)
 	received2 := e2e.WsReader(suite.testState.ws2)
 
@@ -259,8 +251,6 @@ func (suite *OnChainOffChainTestSuite) TestE2EPrivate() {
 }
 
 func (suite *OnChainOffChainTestSuite) TestE2EBroadcastBlob() {
-	defer suite.testState.done()
-
 	received1 := e2e.WsReader(suite.testState.ws1)
 	received2 := e2e.WsReader(suite.testState.ws2)
 
@@ -285,8 +275,6 @@ func (suite *OnChainOffChainTestSuite) TestE2EBroadcastBlob() {
 }
 
 func (suite *OnChainOffChainTestSuite) TestE2EPrivateBlobDatatypeTagged() {
-	defer suite.testState.done()
-
 	received1 := e2e.WsReader(suite.testState.ws1)
 	received2 := e2e.WsReader(suite.testState.ws2)
 
@@ -317,8 +305,6 @@ func (suite *OnChainOffChainTestSuite) TestE2EPrivateBlobDatatypeTagged() {
 }
 
 func (suite *OnChainOffChainTestSuite) TestE2EWebhookExchange() {
-	defer suite.testState.done()
-
 	received1 := e2e.WsReader(suite.testState.ws1)
 	received2 := e2e.WsReader(suite.testState.ws2)
 
@@ -377,8 +363,6 @@ func (suite *OnChainOffChainTestSuite) TestE2EWebhookExchange() {
 }
 
 func (suite *OnChainOffChainTestSuite) TestE2EWebhookRequestReplyNoTx() {
-	defer suite.testState.done()
-
 	subJSON := fmt.Sprintf(`{
 		"transport": "webhooks",
 		"namespace": "%s",
diff --git a/test/e2e/multiparty/tokens.go b/test/e2e/multiparty/tokens.go
index 1ba4dcc86..e16d330a8 100644
--- a/test/e2e/multiparty/tokens.go
+++ b/test/e2e/multiparty/tokens.go
@@ -1,4 +1,4 @@
-// Copyright © 2022 Kaleido, Inc.
+// Copyright © 2023 Kaleido, Inc.
 //
 // SPDX-License-Identifier: Apache-2.0
 //
@@ -45,11 +45,10 @@ func (suite *TokensTestSuite) BeforeTest(suiteName, testName string) {
 
 func (suite *TokensTestSuite) AfterTest(suiteName, testName string) {
 	e2e.VerifyAllOperationsSucceeded(suite.T(), []*client.FireFlyClient{suite.testState.client1, suite.testState.client2}, suite.testState.startTime)
+	suite.testState.done()
 }
 
 func (suite *TokensTestSuite) TestE2EFungibleTokensAsync() {
-	defer suite.testState.done()
-
 	received1 := e2e.WsReader(suite.testState.ws1)
 	received2 := e2e.WsReader(suite.testState.ws2)
 
@@ -220,8 +219,6 @@ func (suite *TokensTestSuite) TestE2EFungibleTokensAsync() {
 }
 
 func (suite *TokensTestSuite) TestE2ENonFungibleTokensSync() {
-	defer suite.testState.done()
-
 	received1 := e2e.WsReader(suite.testState.ws1)
 	received2 := e2e.WsReader(suite.testState.ws2)
 
diff --git a/test/e2e/multiparty/tokens_remote_name.go b/test/e2e/multiparty/tokens_remote_name.go
index d1a735c99..c6ece024e 100644
--- a/test/e2e/multiparty/tokens_remote_name.go
+++ b/test/e2e/multiparty/tokens_remote_name.go
@@ -1,4 +1,4 @@
-// Copyright © 2022 Kaleido, Inc.
+// Copyright © 2023 Kaleido, Inc.
 //
 // SPDX-License-Identifier: Apache-2.0
 //
@@ -45,11 +45,10 @@ func (suite *TokensRemoteNameTestSuite) BeforeTest(suiteName, testName string) {
 
 func (suite *TokensRemoteNameTestSuite) AfterTest(suiteName, testName string) {
 	e2e.VerifyAllOperationsSucceeded(suite.T(), []*client.FireFlyClient{suite.testState.client1, suite.testState.client2}, suite.testState.startTime)
+	suite.testState.done()
 }
 
 func (suite *TokensRemoteNameTestSuite) TestE2EFungibleTokensWithRemoteNameAsync() {
-	defer suite.testState.done()
-
 	newLocalName := "tokens1"
 
 	config1 := e2e.ReadConfig(suite.T(), suite.testState.configFile1)
diff --git a/test/e2e/runners/ethereum_multiparty_test.go b/test/e2e/runners/ethereum_multiparty_test.go
index 25b297cfa..4f8ef2809 100644
--- a/test/e2e/runners/ethereum_multiparty_test.go
+++ b/test/e2e/runners/ethereum_multiparty_test.go
@@ -28,6 +28,7 @@ func TestEthereumMultipartyE2ESuite(t *testing.T) {
 	suite.Run(t, new(multiparty.IdentityTestSuite))
 	suite.Run(t, new(multiparty.TokensTestSuite))
 	suite.Run(t, new(multiparty.EthereumContractTestSuite))
+	suite.Run(t, new(multiparty.EthereumTokenContractTestSuite))
 	suite.Run(t, new(multiparty.ContractMigrationTestSuite))
 	suite.Run(t, new(multiparty.NamespaceAliasSuite))
 }
diff --git a/test/e2e/runners/ethereum_namespace.go b/test/e2e/runners/ethereum_namespace.go
index 0faa2d7b5..2476ad9ba 100644
--- a/test/e2e/runners/ethereum_namespace.go
+++ b/test/e2e/runners/ethereum_namespace.go
@@ -1,4 +1,4 @@
-// Copyright © 2022 Kaleido, Inc.
+// Copyright © 2023 Kaleido, Inc.
 //
 // SPDX-License-Identifier: Apache-2.0
 //
@@ -27,4 +27,5 @@ func TestEthereumNamespaceE2ESuite(t *testing.T) {
 	suite.Run(t, new(multiparty.OnChainOffChainTestSuite))
 	suite.Run(t, new(multiparty.TokensTestSuite))
 	suite.Run(t, new(multiparty.EthereumContractTestSuite))
+	suite.Run(t, new(multiparty.EthereumTokenContractTestSuite))
 }