Repository: hyperledger/firefly
Pull request #913 (2022-07-28 21:51:55)

Use "poolData" instead of "namespace" in fftokens
Depends on:
https://github.com/hyperledger/firefly-tokens-erc1155/pull/84
https://github.com/hyperledger/firefly-tokens-erc20-erc721/pull/70


Patch:
diff --git a/internal/events/token_pool_created.go b/internal/events/token_pool_created.go
index 2ca1f09e0..7ea1b31f2 100644
--- a/internal/events/token_pool_created.go
+++ b/internal/events/token_pool_created.go
@@ -50,19 +50,21 @@ func addPoolDetailsFromPlugin(ffPool *core.TokenPool, pluginPool *tokens.TokenPo
 }
 
 func (em *eventManager) confirmPool(ctx context.Context, pool *core.TokenPool, ev *blockchain.Event) error {
-	if ev.ProtocolID != "" || ev.BlockchainTXID != "" {
+	var blockchainID string
+	if ev != nil {
 		// Some pools will not include a blockchain event for creation (such as when indexing a pre-existing pool)
+		blockchainID = ev.BlockchainTXID
 		chainEvent := buildBlockchainEvent(pool.Namespace, nil, ev, &core.BlockchainTransactionRef{
 			ID:           pool.TX.ID,
 			Type:         pool.TX.Type,
-			BlockchainID: ev.BlockchainTXID,
+			BlockchainID: blockchainID,
 		})
 		if err := em.maybePersistBlockchainEvent(ctx, chainEvent, nil); err != nil {
 			return err
 		}
 		em.emitBlockchainEventMetric(ev)
 	}
-	if _, err := em.txHelper.PersistTransaction(ctx, pool.TX.ID, pool.TX.Type, ev.BlockchainTXID); err != nil {
+	if _, err := em.txHelper.PersistTransaction(ctx, pool.TX.ID, pool.TX.Type, blockchainID); err != nil {
 		return err
 	}
 	pool.State = core.TokenPoolStateConfirmed
@@ -151,7 +153,7 @@ func (em *eventManager) TokenPoolCreated(ti tokens.Plugin, pool *tokens.TokenPoo
 					return nil // already confirmed
 				}
 				msgIDforRewind = existingPool.Message
-				return em.confirmPool(ctx, existingPool, &pool.Event)
+				return em.confirmPool(ctx, existingPool, pool.Event)
 			} else if pool.TX.ID == nil {
 				// TransactionID is required if the pool doesn't exist yet
 				// (but it may be omitted when activating a pool that was received via definition broadcast)
@@ -182,11 +184,10 @@ func (em *eventManager) TokenPoolCreated(ti tokens.Plugin, pool *tokens.TokenPoo
 		// 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 {
-			broadcast := &core.TokenPoolAnnouncement{
-				Pool: announcePool,
-			}
 			log.L(em.ctx).Infof("Announcing token pool, id=%s", announcePool.ID)
-			err = em.defsender.DefineTokenPool(em.ctx, broadcast, false)
+			err = em.defsender.DefineTokenPool(em.ctx, &core.TokenPoolAnnouncement{
+				Pool: announcePool,
+			}, false)
 		}
 	}
 
diff --git a/internal/events/tokens_approved.go b/internal/events/tokens_approved.go
index 6667263c0..c1b99a490 100644
--- a/internal/events/tokens_approved.go
+++ b/internal/events/tokens_approved.go
@@ -100,7 +100,7 @@ func (em *eventManager) persistTokenApproval(ctx context.Context, approval *toke
 		}
 	}
 
-	chainEvent := buildBlockchainEvent(approval.Namespace, nil, &approval.Event, &core.BlockchainTransactionRef{
+	chainEvent := buildBlockchainEvent(approval.Namespace, nil, approval.Event, &core.BlockchainTransactionRef{
 		ID:           approval.TX.ID,
 		Type:         approval.TX.Type,
 		BlockchainID: approval.Event.BlockchainTXID,
@@ -108,7 +108,7 @@ func (em *eventManager) persistTokenApproval(ctx context.Context, approval *toke
 	if err := em.maybePersistBlockchainEvent(ctx, chainEvent, nil); err != nil {
 		return false, err
 	}
-	em.emitBlockchainEventMetric(&approval.Event)
+	em.emitBlockchainEventMetric(approval.Event)
 	approval.BlockchainEvent = chainEvent.ID
 
 	fb := database.TokenApprovalQueryFactory.NewFilter(ctx)
diff --git a/internal/events/tokens_transferred.go b/internal/events/tokens_transferred.go
index 198c6a392..cdd833a23 100644
--- a/internal/events/tokens_transferred.go
+++ b/internal/events/tokens_transferred.go
@@ -100,7 +100,7 @@ func (em *eventManager) persistTokenTransfer(ctx context.Context, transfer *toke
 		}
 	}
 
-	chainEvent := buildBlockchainEvent(pool.Namespace, nil, &transfer.Event, &core.BlockchainTransactionRef{
+	chainEvent := buildBlockchainEvent(pool.Namespace, nil, transfer.Event, &core.BlockchainTransactionRef{
 		ID:           transfer.TX.ID,
 		Type:         transfer.TX.Type,
 		BlockchainID: transfer.Event.BlockchainTXID,
@@ -108,7 +108,7 @@ func (em *eventManager) persistTokenTransfer(ctx context.Context, transfer *toke
 	if err := em.maybePersistBlockchainEvent(ctx, chainEvent, nil); err != nil {
 		return false, err
 	}
-	em.emitBlockchainEventMetric(&transfer.Event)
+	em.emitBlockchainEventMetric(transfer.Event)
 	transfer.BlockchainEvent = chainEvent.ID
 
 	if err := em.database.UpsertTokenTransfer(ctx, &transfer.TokenTransfer); err != nil {
@@ -153,7 +153,7 @@ func (em *eventManager) TokensTransferred(ti tokens.Plugin, transfer *tokens.Tok
 					msgIDforRewind = transfer.Message
 				}
 			}
-			em.emitBlockchainEventMetric(&transfer.Event)
+			em.emitBlockchainEventMetric(transfer.Event)
 
 			event := core.NewEvent(core.EventTypeTransferConfirmed, transfer.Namespace, transfer.LocalID, transfer.TX.ID, transfer.Pool.String())
 			return em.database.InsertEvent(ctx, event)
diff --git a/internal/orchestrator/orchestrator.go b/internal/orchestrator/orchestrator.go
index fc2997613..04be69396 100644
--- a/internal/orchestrator/orchestrator.go
+++ b/internal/orchestrator/orchestrator.go
@@ -380,9 +380,7 @@ func (or *orchestrator) initHandlers(ctx context.Context) (err error) {
 	}
 
 	for _, token := range or.plugins.Tokens {
-		if err := token.Plugin.SetHandler(or.namespace.LocalName, or.events); err != nil {
-			return err
-		}
+		token.Plugin.SetHandler(or.namespace.LocalName, or.events)
 		token.Plugin.SetOperationHandler(or.namespace.LocalName, &or.bc)
 	}
 
diff --git a/internal/tokens/fftokens/fftokens.go b/internal/tokens/fftokens/fftokens.go
index 9b1e4035e..15d125a29 100644
--- a/internal/tokens/fftokens/fftokens.go
+++ b/internal/tokens/fftokens/fftokens.go
@@ -43,64 +43,58 @@ type FFTokens struct {
 }
 
 type callbacks struct {
+	plugin     tokens.Plugin
 	handlers   map[string]tokens.Callbacks
 	opHandlers map[string]core.OperationCallbacks
 }
 
-func (cb *callbacks) OperationUpdate(ctx context.Context, plugin tokens.Plugin, nsOpID string, status core.OpStatus, blockchainTXID, errorMessage string, opOutput fftypes.JSONObject) {
+func (cb *callbacks) OperationUpdate(ctx context.Context, nsOpID string, status core.OpStatus, blockchainTXID, errorMessage string, opOutput fftypes.JSONObject) {
 	namespace, _, _ := core.ParseNamespacedOpID(ctx, nsOpID)
 	if handler, ok := cb.opHandlers[namespace]; ok {
-		handler.OperationUpdate(plugin, nsOpID, status, blockchainTXID, errorMessage, opOutput)
+		handler.OperationUpdate(cb.plugin, nsOpID, status, blockchainTXID, errorMessage, opOutput)
 	} else {
 		log.L(ctx).Errorf("No handler found for token operation '%s'", nsOpID)
 	}
 }
 
-func (cb *callbacks) TokenPoolCreated(ctx context.Context, namespace string, plugin tokens.Plugin, pool *tokens.TokenPool) error {
-	if namespace == "" {
-		// Older token subscriptions don't populate namespace, so deliver the event to every handler
-		for _, cb := range cb.handlers {
-			if err := cb.TokenPoolCreated(plugin, pool); err != nil {
-				return err
-			}
-		}
-	} else {
-		if handler, ok := cb.handlers[namespace]; ok {
-			return handler.TokenPoolCreated(plugin, pool)
+func (cb *callbacks) TokenPoolCreated(ctx context.Context, pool *tokens.TokenPool) error {
+	// Deliver token pool creation events to every handler
+	for _, handler := range cb.handlers {
+		if err := handler.TokenPoolCreated(cb.plugin, pool); err != nil {
+			return err
 		}
-		log.L(ctx).Errorf("No handler found for token pool event on namespace '%s'", namespace)
 	}
 	return nil
 }
 
-func (cb *callbacks) TokensTransferred(ctx context.Context, namespace string, plugin tokens.Plugin, transfer *tokens.TokenTransfer) error {
+func (cb *callbacks) TokensTransferred(ctx context.Context, namespace string, transfer *tokens.TokenTransfer) error {
 	if namespace == "" {
 		// Older token subscriptions don't populate namespace, so deliver the event to every handler
-		for _, cb := range cb.handlers {
-			if err := cb.TokensTransferred(plugin, transfer); err != nil {
+		for _, handler := range cb.handlers {
+			if err := handler.TokensTransferred(cb.plugin, transfer); err != nil {
 				return err
 			}
 		}
 	} else {
 		if handler, ok := cb.handlers[namespace]; ok {
-			return handler.TokensTransferred(plugin, transfer)
+			return handler.TokensTransferred(cb.plugin, transfer)
 		}
 		log.L(ctx).Errorf("No handler found for token transfer event on namespace '%s'", namespace)
 	}
 	return nil
 }
 
-func (cb *callbacks) TokensApproved(ctx context.Context, namespace string, plugin tokens.Plugin, approval *tokens.TokenApproval) error {
+func (cb *callbacks) TokensApproved(ctx context.Context, namespace string, approval *tokens.TokenApproval) error {
 	if namespace == "" {
 		// Older token subscriptions don't populate namespace, so deliver the event to every handler
-		for _, cb := range cb.handlers {
-			if err := cb.TokensApproved(plugin, approval); err != nil {
+		for _, handler := range cb.handlers {
+			if err := handler.TokensApproved(cb.plugin, approval); err != nil {
 				return err
 			}
 		}
 	} else {
 		if handler, ok := cb.handlers[namespace]; ok {
-			return handler.TokensApproved(plugin, approval)
+			return handler.TokensApproved(cb.plugin, approval)
 		}
 		log.L(ctx).Errorf("No handler found for token approval event on namespace '%s'", namespace)
 	}
@@ -132,10 +126,6 @@ type tokenData struct {
 	MessageHash *fftypes.Bytes32     `json:"messageHash,omitempty"`
 }
 
-type tokenInit struct {
-	Namespace string `json:"namespace"`
-}
-
 type createPool struct {
 	Type      core.TokenType     `json:"type"`
 	RequestID string             `json:"requestId"`
@@ -147,7 +137,7 @@ type createPool struct {
 }
 
 type activatePool struct {
-	Namespace   string             `json:"namespace"`
+	PoolData    string             `json:"poolData"`
 	PoolLocator string             `json:"poolLocator"`
 	Config      fftypes.JSONObject `json:"config"`
 	RequestID   string             `json:"requestId,omitempty"`
@@ -211,8 +201,11 @@ func (ft *FFTokens) Init(ctx context.Context, name string, config config.Section
 	ft.ctx = log.WithLogField(ctx, "proto", "fftokens")
 	ft.configuredName = name
 	ft.capabilities = &tokens.Capabilities{}
-	ft.callbacks.handlers = make(map[string]tokens.Callbacks)
-	ft.callbacks.opHandlers = make(map[string]core.OperationCallbacks)
+	ft.callbacks = callbacks{
+		plugin:     ft,
+		handlers:   make(map[string]tokens.Callbacks),
+		opHandlers: make(map[string]core.OperationCallbacks),
+	}
 
 	if config.GetString(ffresty.HTTPConfigURL) == "" {
 		return i18n.NewError(ctx, coremsgs.MsgMissingPluginConfig, "url", "tokens.fftokens")
@@ -234,18 +227,8 @@ func (ft *FFTokens) Init(ctx context.Context, name string, config config.Section
 	return nil
 }
 
-func (ft *FFTokens) SetHandler(namespace string, handler tokens.Callbacks) error {
+func (ft *FFTokens) SetHandler(namespace string, handler tokens.Callbacks) {
 	ft.callbacks.handlers[namespace] = handler
-
-	res, err := ft.client.R().SetContext(ft.ctx).
-		SetBody(&tokenInit{
-			Namespace: namespace,
-		}).
-		Post("/api/v1/init")
-	if err != nil || !res.IsSuccess() {
-		return wrapError(ft.ctx, nil, res, err)
-	}
-	return nil
 }
 
 func (ft *FFTokens) SetOperationHandler(namespace string, handler core.OperationCallbacks) {
@@ -276,35 +259,53 @@ func (ft *FFTokens) handleReceipt(ctx context.Context, data fftypes.JSONObject)
 		updateType = core.OpStatusFailed
 	}
 	l.Infof("Received operation update: status=%s request=%s message=%s", updateType, requestID, message)
-	ft.callbacks.OperationUpdate(ctx, ft, requestID, updateType, transactionHash, message, data)
+	ft.callbacks.OperationUpdate(ctx, requestID, updateType, transactionHash, message, data)
 }
 
-func (ft *FFTokens) handleTokenPoolCreate(ctx context.Context, data fftypes.JSONObject) (err error) {
-	tokenType := data.GetString("type")
-	poolLocator := data.GetString("poolLocator")
-	standard := data.GetString("standard")   // optional
-	symbol := data.GetString("symbol")       // optional
-	decimals := data.GetInt64("decimals")    // optional
-	info := data.GetObject("info")           // optional
-	namespace := data.GetString("namespace") // optional
-
-	// All blockchain items below are optional
-	blockchainEvent := data.GetObject("blockchain")
-	blockchainID := blockchainEvent.GetString("id")
-	blockchainInfo := blockchainEvent.GetObject("info")
+func (ft *FFTokens) buildBlockchainEvent(eventData fftypes.JSONObject) *blockchain.Event {
+	blockchainID := eventData.GetString("id")
+	blockchainInfo := eventData.GetObject("info")
 	txHash := blockchainInfo.GetString("transactionHash")
-	timestampStr := blockchainEvent.GetString("timestamp")
 
-	timestamp, err := fftypes.ParseTimeString(timestampStr)
-	if err != nil {
-		timestamp = fftypes.Now()
+	// Only include a blockchain event if there was some significant blockchain info
+	if blockchainID != "" || txHash != "" {
+		timestampStr := eventData.GetString("timestamp")
+		timestamp, err := fftypes.ParseTimeString(timestampStr)
+		if err != nil {
+			timestamp = fftypes.Now()
+		}
+
+		return &blockchain.Event{
+			ProtocolID:     blockchainID,
+			BlockchainTXID: txHash,
+			Source:         ft.Name() + ":" + ft.configuredName,
+			Name:           eventData.GetString("name"),
+			Output:         eventData.GetObject("output"),
+			Location:       eventData.GetString("location"),
+			Signature:      eventData.GetString("signature"),
+			Info:           blockchainInfo,
+			Timestamp:      timestamp,
+		}
 	}
+	return nil
+}
+
+func (ft *FFTokens) handleTokenPoolCreate(ctx context.Context, data fftypes.JSONObject) (err error) {
+	tokenType := data.GetString("type")
+	poolLocator := data.GetString("poolLocator")
 
 	if tokenType == "" || poolLocator == "" {
 		log.L(ctx).Errorf("TokenPool event is not valid - missing data: %+v", data)
 		return nil // move on
 	}
 
+	// These fields are optional
+	standard := data.GetString("standard")
+	symbol := data.GetString("symbol")
+	decimals := data.GetInt64("decimals")
+	info := data.GetObject("info")
+	blockchainEvent := data.GetObject("blockchain")
+
 	// We want to process all events, even those not initiated by FireFly.
 	// The "data" argument is optional, so it's important not to fail if it's missing or malformed.
 	poolDataString := data.GetString("data")
@@ -331,25 +332,11 @@ func (ft *FFTokens) handleTokenPoolCreate(ctx context.Context, data fftypes.JSON
 		Symbol:    symbol,
 		Decimals:  int(decimals),
 		Info:      info,
-	}
-
-	// Only include a blockchain event if there was some significant blockchain info
-	if blockchainID != "" || txHash != "" {
-		pool.Event = blockchain.Event{
-			ProtocolID:     blockchainID,
-			BlockchainTXID: txHash,
-			Source:         ft.Name() + ":" + ft.configuredName,
-			Name:           blockchainEvent.GetString("name"),
-			Output:         blockchainEvent.GetObject("output"),
-			Location:       blockchainEvent.GetString("location"),
-			Signature:      blockchainEvent.GetString("signature"),
-			Info:           blockchainInfo,
-			Timestamp:      timestamp,
-		}
+		Event:     ft.buildBlockchainEvent(blockchainEvent),
 	}
 
 	// If there's an error dispatching the event, we must return the error and shutdown
-	return ft.callbacks.TokenPoolCreated(ctx, namespace, ft, pool)
+	return ft.callbacks.TokenPoolCreated(ctx, pool)
 }
 
 func (ft *FFTokens) handleTokenTransfer(ctx context.Context, t core.TokenTransferType, data fftypes.JSONObject) (err error) {
@@ -359,31 +346,24 @@ func (ft *FFTokens) handleTokenTransfer(ctx context.Context, t core.TokenTransfe
 	fromAddress := data.GetString("from")
 	toAddress := data.GetString("to")
 	value := data.GetString("amount")
-	tokenIndex := data.GetString("tokenIndex") // optional
-	uri := data.GetString("uri")               // optional
-	namespace := data.GetString("namespace")   // optional
-
-	blockchainEvent := data.GetObject("blockchain")
-	blockchainID := blockchainEvent.GetString("id")
-	blockchainInfo := blockchainEvent.GetObject("info")
-	txHash := blockchainInfo.GetString("transactionHash")  // optional
-	timestampStr := blockchainEvent.GetString("timestamp") // optional
-
-	timestamp, err := fftypes.ParseTimeString(timestampStr)
-	if err != nil {
-		timestamp = fftypes.Now()
-	}
+	blockchainEvent := ft.buildBlockchainEvent(data.GetObject("blockchain"))
 
 	if protocolID == "" ||
 		poolLocator == "" ||
 		signerAddress == "" ||
 		value == "" ||
 		(t != core.TokenTransferTypeMint && fromAddress == "") ||
-		(t != core.TokenTransferTypeBurn && toAddress == "") {
+		(t != core.TokenTransferTypeBurn && toAddress == "") ||
+		blockchainEvent == nil {
 		log.L(ctx).Errorf("%s event is not valid - missing data: %+v", t, data)
 		return nil // move on
 	}
 
+	// These fields are optional
+	tokenIndex := data.GetString("tokenIndex")
+	uri := data.GetString("uri")
+	namespace := data.GetString("poolData")
+
 	// We want to process all events, even those not initiated by FireFly.
 	// The "data" argument is optional, so it's important not to fail if it's missing or malformed.
 	transferDataString := data.GetString("data")
@@ -424,21 +404,11 @@ func (ft *FFTokens) handleTokenTransfer(ctx context.Context, t core.TokenTransfe
 				Type: txType,
 			},
 		},
-		Event: blockchain.Event{
-			ProtocolID:     blockchainID,
-			BlockchainTXID: txHash,
-			Source:         ft.Name() + ":" + ft.configuredName,
-			Name:           blockchainEvent.GetString("name"),
-			Output:         blockchainEvent.GetObject("output"),
-			Location:       blockchainEvent.GetString("location"),
-			Signature:      blockchainEvent.GetString("signature"),
-			Info:           blockchainInfo,
-			Timestamp:      timestamp,
-		},
+		Event: blockchainEvent,
 	}
 
 	// If there's an error dispatching the event, we must return the error and shutdown
-	return ft.callbacks.TokensTransferred(ctx, namespace, ft, transfer)
+	return ft.callbacks.TokensTransferred(ctx, namespace, transfer)
 }
 
 func (ft *FFTokens) handleTokenApproval(ctx context.Context, data fftypes.JSONObject) (err error) {
@@ -448,29 +418,22 @@ func (ft *FFTokens) handleTokenApproval(ctx context.Context, data fftypes.JSONOb
 	poolLocator := data.GetString("poolLocator")
 	operatorAddress := data.GetString("operator")
 	approved := data.GetBool("approved")
-	info := data.GetObject("info")           // optional
-	namespace := data.GetString("namespace") // optional
-
-	blockchainEvent := data.GetObject("blockchain")
-	blockchainID := blockchainEvent.GetString("id")
-	blockchainInfo := blockchainEvent.GetObject("info")
-	txHash := blockchainInfo.GetString("transactionHash")  // optional
-	timestampStr := blockchainEvent.GetString("timestamp") // optional
-
-	timestamp, err := fftypes.ParseTimeString(timestampStr)
-	if err != nil {
-		timestamp = fftypes.Now()
-	}
+	blockchainEvent := ft.buildBlockchainEvent(data.GetObject("blockchain"))
 
 	if protocolID == "" ||
 		subject == "" ||
 		poolLocator == "" ||
 		signerAddress == "" ||
-		operatorAddress == "" {
+		operatorAddress == "" ||
+		blockchainEvent == nil {
 		log.L(ctx).Errorf("Approval event is not valid - missing data: %+v", data)
 		return nil // move on
 	}
 
+	// These fields are optional
+	info := data.GetObject("info")
+	namespace := data.GetString("poolData")
+
 	// We want to process all events, even those not initiated by FireFly.
 	// The "data" argument is optional, so it's important not to fail if it's missing or malformed.
 	approvalDataString := data.GetString("data")
@@ -500,20 +463,10 @@ func (ft *FFTokens) handleTokenApproval(ctx context.Context, data fftypes.JSONOb
 				Type: txType,
 			},
 		},
-		Event: blockchain.Event{
-			ProtocolID:     blockchainID,
-			BlockchainTXID: txHash,
-			Source:         ft.Name() + ":" + ft.configuredName,
-			Name:           blockchainEvent.GetString("name"),
-			Output:         blockchainEvent.GetObject("output"),
-			Location:       blockchainEvent.GetString("location"),
-			Signature:      blockchainEvent.GetString("signature"),
-			Info:           blockchainInfo,
-			Timestamp:      timestamp,
-		},
+		Event: blockchainEvent,
 	}
 
-	return ft.callbacks.TokensApproved(ctx, namespace, ft, approval)
+	return ft.callbacks.TokensApproved(ctx, namespace, approval)
 }
 
 func (ft *FFTokens) handleMessage(ctx context.Context, msgBytes []byte) (err error) {
@@ -640,7 +593,7 @@ func (ft *FFTokens) ActivateTokenPool(ctx context.Context, nsOpID string, pool *
 	res, err := ft.client.R().SetContext(ctx).
 		SetBody(&activatePool{
 			RequestID:   nsOpID,
-			Namespace:   pool.Namespace,
+			PoolData:    pool.Namespace,
 			PoolLocator: pool.Locator,
 			Config:      pool.Config,
 		}).
diff --git a/manifest.json b/manifest.json
index 95ef3dbe7..aaa506e5f 100644
--- a/manifest.json
+++ b/manifest.json
@@ -16,13 +16,13 @@
   },
   "tokens-erc1155": {
     "image": "ghcr.io/hyperledger/firefly-tokens-erc1155",
-    "tag": "v1.0.2-20220617-31",
-    "sha": "c2b9db025f80cd45fb1a7694d67556936ae050d3fc269bf2f6190f380e9de44c"
+    "tag": "v1.0.3-20220801-35",
+    "sha": "09a91c1488c71588f6c051e227068dc5c3e05bf07de714ad4269c011a0d65670"
   },
   "tokens-erc20-erc721": {
     "image": "ghcr.io/hyperledger/firefly-tokens-erc20-erc721",
-    "tag": "v1.0.1-20220617-47",
-    "sha": "533eb4e3197b1a65ff85cb47e685c56a8454f2b2aa1f69bc91ce47475df0f5d7"
+    "tag": "v1.0.2-20220801-54",
+    "sha": "d48f75a5b65d7583965f7999958b64e1b7c3b85fe0234e388521783fb17aa73a"
   },
   "build": {
     "firefly-builder": {
diff --git a/mocks/tokenmocks/plugin.go b/mocks/tokenmocks/plugin.go
index 5ce8645c4..20dbc8722 100644
--- a/mocks/tokenmocks/plugin.go
+++ b/mocks/tokenmocks/plugin.go
@@ -139,17 +139,8 @@ func (_m *Plugin) Name() string {
 }
 
 // SetHandler provides a mock function with given fields: namespace, handler
-func (_m *Plugin) SetHandler(namespace string, handler tokens.Callbacks) error {
-	ret := _m.Called(namespace, handler)
-
-	var r0 error
-	if rf, ok := ret.Get(0).(func(string, tokens.Callbacks) error); ok {
-		r0 = rf(namespace, handler)
-	} else {
-		r0 = ret.Error(0)
-	}
-
-	return r0
+func (_m *Plugin) SetHandler(namespace string, handler tokens.Callbacks) {
+	_m.Called(namespace, handler)
 }
 
 // SetOperationHandler provides a mock function with given fields: namespace, handler
diff --git a/pkg/tokens/plugin.go b/pkg/tokens/plugin.go
index d4783391e..adc710ed2 100644
--- a/pkg/tokens/plugin.go
+++ b/pkg/tokens/plugin.go
@@ -37,7 +37,7 @@ type Plugin interface {
 
 	// SetHandler registers a handler to receive callbacks
 	// Plugin will attempt (but is not guaranteed) to deliver events only for the given namespace
-	SetHandler(namespace string, handler Callbacks) error
+	SetHandler(namespace string, handler Callbacks)
 
 	// SetOperationHandler registers a handler to receive async operation status
 	// If namespace is set, plugin will attempt to deliver only events for that namespace
@@ -122,7 +122,7 @@ type TokenPool struct {
 	Info fftypes.JSONObject
 
 	// Event contains info on the underlying blockchain event for this pool creation
-	Event blockchain.Event
+	Event *blockchain.Event
 }
 
 type TokenTransfer struct {
@@ -134,7 +134,7 @@ type TokenTransfer struct {
 	PoolLocator string
 
 	// Event contains info on the underlying blockchain event for this transfer
-	Event blockchain.Event
+	Event *blockchain.Event
 }
 
 type TokenApproval struct {
@@ -144,5 +144,5 @@ type TokenApproval struct {
 	PoolLocator string
 
 	// Event contains info on the underlying blockchain event for this transfer
-	Event blockchain.Event
+	Event *blockchain.Event
 }


Test patch:
diff --git a/internal/events/token_pool_created_test.go b/internal/events/token_pool_created_test.go
index 452ae1246..7f7461e12 100644
--- a/internal/events/token_pool_created_test.go
+++ b/internal/events/token_pool_created_test.go
@@ -51,7 +51,7 @@ func TestTokenPoolCreatedIgnore(t *testing.T) {
 			Type: core.TransactionTypeTokenPool,
 		},
 		Connector: "erc1155",
-		Event: blockchain.Event{
+		Event: &blockchain.Event{
 			BlockchainTXID: "0xffffeeee",
 			ProtocolID:     "tx1",
 			Info:           info,
@@ -78,7 +78,7 @@ func TestTokenPoolCreatedIgnoreNoTX(t *testing.T) {
 		Type:        core.TokenTypeFungible,
 		PoolLocator: "123",
 		Connector:   "erc1155",
-		Event: blockchain.Event{
+		Event: &blockchain.Event{
 			BlockchainTXID: "0xffffeeee",
 			ProtocolID:     "tx1",
 			Info:           info,
@@ -113,7 +113,7 @@ func TestTokenPoolCreatedConfirm(t *testing.T) {
 		Standard: "ERC1155",
 		Symbol:   "FFT",
 		Info:     info1,
-		Event: blockchain.Event{
+		Event: &blockchain.Event{
 			BlockchainTXID: "0xffffeeee",
 			Name:           "TokenPool",
 			ProtocolID:     "tx1",
@@ -173,7 +173,7 @@ func TestTokenPoolCreatedAlreadyConfirmed(t *testing.T) {
 			ID:   txID,
 			Type: core.TransactionTypeTokenPool,
 		},
-		Event: blockchain.Event{
+		Event: &blockchain.Event{
 			BlockchainTXID: "0xffffeeee",
 			ProtocolID:     "tx1",
 			Info:           info,
@@ -215,7 +215,7 @@ func TestTokenPoolCreatedConfirmFailBadSymbol(t *testing.T) {
 			Type: core.TransactionTypeTokenPool,
 		},
 		Symbol: "ETH",
-		Event: blockchain.Event{
+		Event: &blockchain.Event{
 			BlockchainTXID: "0xffffeeee",
 			ProtocolID:     "tx1",
 			Info:           info,
@@ -261,7 +261,7 @@ func TestTokenPoolCreatedMigrate(t *testing.T) {
 			ID:   txID,
 			Type: core.TransactionTypeTokenPool,
 		},
-		Event: blockchain.Event{
+		Event: &blockchain.Event{
 			BlockchainTXID: "0xffffeeee",
 			ProtocolID:     "tx1",
 			Info:           fftypes.JSONObject{"some": "info"},
@@ -441,7 +441,7 @@ func TestTokenPoolCreatedAnnounce(t *testing.T) {
 			Type: core.TransactionTypeTokenPool,
 		},
 		Connector: "erc1155",
-		Event: blockchain.Event{
+		Event: &blockchain.Event{
 			BlockchainTXID: "0xffffeeee",
 			ProtocolID:     "tx1",
 			Info:           info,
@@ -486,7 +486,7 @@ func TestTokenPoolCreatedAnnounceBadOpInputID(t *testing.T) {
 			Type: core.TransactionTypeTokenPool,
 		},
 		Connector: "erc1155",
-		Event: blockchain.Event{
+		Event: &blockchain.Event{
 			BlockchainTXID: "0xffffeeee",
 			ProtocolID:     "tx1",
 			Info:           info,
@@ -527,7 +527,7 @@ func TestTokenPoolCreatedAnnounceBadOpInputNS(t *testing.T) {
 			Type: core.TransactionTypeTokenPool,
 		},
 		Connector: "erc1155",
-		Event: blockchain.Event{
+		Event: &blockchain.Event{
 			BlockchainTXID: "0xffffeeee",
 			ProtocolID:     "tx1",
 			Info:           info,
@@ -572,7 +572,7 @@ func TestTokenPoolCreatedAnnounceBadSymbol(t *testing.T) {
 		},
 		Connector: "erc1155",
 		Symbol:    "ETH",
-		Event: blockchain.Event{
+		Event: &blockchain.Event{
 			BlockchainTXID: "0xffffeeee",
 			ProtocolID:     "tx1",
 			Info:           info,
diff --git a/internal/events/tokens_approved_test.go b/internal/events/tokens_approved_test.go
index 36bd12dea..bcf14c9d5 100644
--- a/internal/events/tokens_approved_test.go
+++ b/internal/events/tokens_approved_test.go
@@ -49,7 +49,7 @@ func newApproval() *tokens.TokenApproval {
 				ID:   fftypes.NewUUID(),
 			},
 		},
-		Event: blockchain.Event{
+		Event: &blockchain.Event{
 			BlockchainTXID: "0xffffeeee",
 			Name:           "TokenApproval",
 			ProtocolID:     "0000/0000/0000",
diff --git a/internal/events/tokens_transferred_test.go b/internal/events/tokens_transferred_test.go
index 61e9afe3c..33dedfbf0 100644
--- a/internal/events/tokens_transferred_test.go
+++ b/internal/events/tokens_transferred_test.go
@@ -49,7 +49,7 @@ func newTransfer() *tokens.TokenTransfer {
 				Type: core.TransactionTypeTokenTransfer,
 			},
 		},
-		Event: blockchain.Event{
+		Event: &blockchain.Event{
 			BlockchainTXID: "0xffffeeee",
 			Name:           "Transfer",
 			ProtocolID:     "0000/0000/0000",
@@ -370,7 +370,7 @@ func TestTokensTransferredWithMessageReceived(t *testing.T) {
 			Message:    fftypes.NewUUID(),
 			Amount:     *fftypes.NewFFBigInt(1),
 		},
-		Event: blockchain.Event{
+		Event: &blockchain.Event{
 			BlockchainTXID: "0xffffeeee",
 			ProtocolID:     "0000/0000/0000",
 			Info:           info,
@@ -433,7 +433,7 @@ func TestTokensTransferredWithMessageSend(t *testing.T) {
 			Message:    fftypes.NewUUID(),
 			Amount:     *fftypes.NewFFBigInt(1),
 		},
-		Event: blockchain.Event{
+		Event: &blockchain.Event{
 			BlockchainTXID: "0xffffeeee",
 			ProtocolID:     "0000/0000/0000",
 			Info:           info,
diff --git a/internal/orchestrator/orchestrator_test.go b/internal/orchestrator/orchestrator_test.go
index dfd7c0cde..6474dabe2 100644
--- a/internal/orchestrator/orchestrator_test.go
+++ b/internal/orchestrator/orchestrator_test.go
@@ -230,21 +230,6 @@ func TestInitOK(t *testing.T) {
 	assert.Equal(t, or.mmp, or.MultiParty())
 }
 
-func TestInitTokenListenerFail(t *testing.T) {
-	or := newTestOrchestrator()
-	defer or.cleanup(t)
-	or.mdi.On("SetHandler", "ns", mock.Anything).Return()
-	or.mbi.On("SetHandler", "ns", mock.Anything).Return()
-	or.mbi.On("SetOperationHandler", "ns", mock.Anything).Return()
-	or.mdi.On("GetIdentities", mock.Anything, "ns", mock.Anything).Return([]*core.Identity{{}}, nil, nil)
-	or.mdx.On("SetHandler", "ns", mock.Anything).Return()
-	or.mdx.On("SetNodes", mock.Anything).Return()
-	or.mps.On("SetHandler", "ns", mock.Anything).Return()
-	or.mti.On("SetHandler", "ns", mock.Anything).Return(fmt.Errorf("pop"))
-	err := or.initHandlers(context.Background())
-	assert.EqualError(t, err, "pop")
-}
-
 func TestInitDataexchangeNodesFail(t *testing.T) {
 	or := newTestOrchestrator()
 	defer or.cleanup(t)
diff --git a/internal/tokens/fftokens/fftokens_test.go b/internal/tokens/fftokens/fftokens_test.go
index 7190c0d8f..658c6572a 100644
--- a/internal/tokens/fftokens/fftokens_test.go
+++ b/internal/tokens/fftokens/fftokens_test.go
@@ -238,8 +238,6 @@ func TestCreateTokenPoolSynchronous(t *testing.T) {
 		Symbol: "symbol",
 	}
 
-	httpmock.RegisterResponder("POST", fmt.Sprintf("%s/api/v1/init", httpURL),
-		httpmock.NewStringResponder(204, ""))
 	httpmock.RegisterResponder("POST", fmt.Sprintf("%s/api/v1/createpool", httpURL),
 		func(req *http.Request) (*http.Response, error) {
 			body := make(fftypes.JSONObject)
@@ -263,8 +261,7 @@ func TestCreateTokenPoolSynchronous(t *testing.T) {
 		})
 
 	mcb := &tokenmocks.Callbacks{}
-	err := h.SetHandler("ns1", mcb)
-	assert.NoError(t, err)
+	h.SetHandler("ns1", mcb)
 
 	mcb.On("TokenPoolCreated", h, mock.MatchedBy(func(p *tokens.TokenPool) bool {
 		return p.PoolLocator == "F1" && p.Type == core.TokenTypeFungible && *p.TX.ID == *pool.TX.ID
@@ -339,7 +336,7 @@ func TestActivateTokenPool(t *testing.T) {
 			err := json.NewDecoder(req.Body).Decode(&body)
 			assert.NoError(t, err)
 			assert.Equal(t, fftypes.JSONObject{
-				"namespace":   "ns1",
+				"poolData":    "ns1",
 				"requestId":   "ns1:" + opID.String(),
 				"poolLocator": "N1",
 				"config":      poolConfig,
@@ -396,15 +393,13 @@ func TestActivateTokenPoolSynchronous(t *testing.T) {
 		Config:    poolConfig,
 	}
 
-	httpmock.RegisterResponder("POST", fmt.Sprintf("%s/api/v1/init", httpURL),
-		httpmock.NewStringResponder(204, ""))
 	httpmock.RegisterResponder("POST", fmt.Sprintf("%s/api/v1/activatepool", 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{
-				"namespace":   "ns1",
+				"poolData":    "ns1",
 				"requestId":   "ns1:" + opID.String(),
 				"poolLocator": "N1",
 				"config":      poolConfig,
@@ -427,7 +422,7 @@ func TestActivateTokenPoolSynchronous(t *testing.T) {
 	mcb := &tokenmocks.Callbacks{}
 	h.SetHandler("ns1", mcb)
 	mcb.On("TokenPoolCreated", h, mock.MatchedBy(func(p *tokens.TokenPool) bool {
-		return p.PoolLocator == "F1" && p.Type == core.TokenTypeFungible && p.TX.ID == nil && p.Event.ProtocolID == ""
+		return p.PoolLocator == "F1" && p.Type == core.TokenTypeFungible && p.TX.ID == nil && p.Event == nil
 	})).Return(nil)
 
 	complete, err := h.ActivateTokenPool(context.Background(), nsOpID, pool)
@@ -450,15 +445,13 @@ func TestActivateTokenPoolSynchronousBadResponse(t *testing.T) {
 		Config:    poolConfig,
 	}
 
-	httpmock.RegisterResponder("POST", fmt.Sprintf("%s/api/v1/init", httpURL),
-		httpmock.NewStringResponder(204, ""))
 	httpmock.RegisterResponder("POST", fmt.Sprintf("%s/api/v1/activatepool", 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{
-				"namespace":   "ns1",
+				"poolData":    "ns1",
 				"requestId":   "ns1:" + opID.String(),
 				"poolLocator": "N1",
 				"config":      poolConfig,
@@ -506,7 +499,7 @@ func TestActivateTokenPoolNoContent(t *testing.T) {
 			err := json.NewDecoder(req.Body).Decode(&body)
 			assert.NoError(t, err)
 			assert.Equal(t, fftypes.JSONObject{
-				"namespace":   "ns1",
+				"poolData":    "ns1",
 				"requestId":   "ns1:" + opID.String(),
 				"poolLocator": "N1",
 				"config":      poolConfig,
@@ -919,7 +912,7 @@ func TestPoolEvents(t *testing.T) {
 		"event": "token-pool",
 		"data": fftypes.JSONObject{
 			"id":          "000000000010/000020/000030/000040",
-			"namespace":   "ns1",
+			"poolData":    "ns1",
 			"type":        "fungible",
 			"poolLocator": "F1",
 			"signer":      "0x0",
@@ -1013,7 +1006,7 @@ func TestTransferEvents(t *testing.T) {
 		"event": "token-mint",
 		"data": fftypes.JSONObject{
 			"id":          "000000000010/000020/000030/000040",
-			"namespace":   "ns1",
+			"poolData":    "ns1",
 			"poolLocator": "F1",
 			"signer":      "0x0",
 			"to":          "0x0",
@@ -1195,14 +1188,18 @@ func TestApprovalEvents(t *testing.T) {
 
 	// token-approval: success
 	mcb.On("TokensApproved", h, mock.MatchedBy(func(t *tokens.TokenApproval) bool {
-		return t.Approved == true && t.Operator == "0x0" && t.PoolLocator == "F1" && t.Event.ProtocolID == "000000000010/000020/000030"
+		return t.Approved == true &&
+			t.Operator == "0x0" &&
+			t.PoolLocator == "F1" &&
+			t.Event != nil &&
+			t.Event.ProtocolID == "000000000010/000020/000030"
 	})).Return(nil).Once()
 	fromServer <- fftypes.JSONObject{
 		"id":    "17",
 		"event": "token-approval",
 		"data": fftypes.JSONObject{
 			"id":          "000000000010/000020/000030/000040",
-			"namespace":   "ns1",
+			"poolData":    "ns1",
 			"subject":     "a:b",
 			"poolLocator": "F1",
 			"signer":      "0x0",
@@ -1234,6 +1231,12 @@ func TestApprovalEvents(t *testing.T) {
 			"signer":      "0x0",
 			"operator":    "0x0",
 			"approved":    true,
+			"blockchain": fftypes.JSONObject{
+				"id": "000000000010/000020/000030",
+				"info": fftypes.JSONObject{
+					"transactionHash": "0xffffeeee",
+				},
+			},
 		},
 	}.String()
 	msg = <-toServer
@@ -1317,8 +1320,7 @@ func TestCallbacksWrongNamespace(t *testing.T) {
 	h, _, _, _, done := newTestFFTokens(t)
 	defer done()
 	nsOpID := "ns1:" + fftypes.NewUUID().String()
-	h.callbacks.OperationUpdate(context.Background(), h, nsOpID, core.OpStatusSucceeded, "tx123", "", nil)
-	h.callbacks.TokenPoolCreated(context.Background(), "ns1", h, nil)
-	h.callbacks.TokensTransferred(context.Background(), "ns1", h, nil)
-	h.callbacks.TokensApproved(context.Background(), "ns1", h, nil)
+	h.callbacks.OperationUpdate(context.Background(), nsOpID, core.OpStatusSucceeded, "tx123", "", nil)
+	h.callbacks.TokensTransferred(context.Background(), "ns1", nil)
+	h.callbacks.TokensApproved(context.Background(), "ns1", nil)
 }