mirror of
https://github.com/hyperledger/fabric-samples.git
synced 2026-06-17 07:25:10 +00:00
130 lines
4.4 KiB
Go
130 lines
4.4 KiB
Go
/*
|
|
Copyright IBM Corp. All Rights Reserved.
|
|
|
|
SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
package service
|
|
|
|
import (
|
|
viewregistry "github.com/hyperledger-labs/fabric-smart-client/platform/view"
|
|
"github.com/hyperledger-labs/fabric-smart-client/platform/view/view"
|
|
"github.com/pkg/errors"
|
|
|
|
token2 "github.com/hyperledger-labs/fabric-token-sdk/token"
|
|
"github.com/hyperledger-labs/fabric-token-sdk/token/services/ttx"
|
|
"github.com/hyperledger-labs/fabric-token-sdk/token/token"
|
|
)
|
|
|
|
// SERVICE
|
|
|
|
// Redeem burns an amount of a certain token. It prepares the transaction,
|
|
// gets it approved by the auditor and sends it to the blockchain for endorsement and commit.
|
|
func (s TokenService) RedeemTokens(tokenType string, quantity uint64, wallet string, message string) (txID string, err error) {
|
|
logger.Infof("redeeming %d %s from [%s] with message [%s]", quantity, tokenType, wallet, message)
|
|
res, err := viewregistry.GetManager(s.FSC).InitiateView(&RedeemView{
|
|
Redeem: &Redeem{
|
|
Wallet: wallet,
|
|
TokenType: tokenType,
|
|
Quantity: quantity,
|
|
Message: message,
|
|
},
|
|
})
|
|
if err != nil {
|
|
logger.Error(err)
|
|
return
|
|
}
|
|
txID, ok := res.(string)
|
|
if !ok {
|
|
err = errors.New("cannot parse redeem response")
|
|
logger.Error(err)
|
|
return
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// VIEW
|
|
|
|
// Redeem contains the input information for a redeem operation
|
|
type Redeem struct {
|
|
// Wallet is the identifier of the wallet that owns the tokens to redeem
|
|
Wallet string
|
|
// TokenIDs contains a list of token ids to redeem. If empty, tokens are selected on the spot.
|
|
TokenIDs []*token.ID
|
|
// TokenType of tokens to redeem
|
|
TokenType string
|
|
// Quantity to redeem
|
|
Quantity uint64
|
|
// Message is an optional user message sent with the transaction.
|
|
// It's stored in the ApplicationMetadata and is sent in the transient field.
|
|
Message string
|
|
}
|
|
|
|
type RedeemView struct {
|
|
*Redeem
|
|
}
|
|
|
|
func (v *RedeemView) Call(context view.Context) (interface{}, error) {
|
|
// specify the auditor and create the envelope for the transaction
|
|
logger.Debug("getting identity of auditor")
|
|
auditor := viewregistry.GetIdentityProvider(context).Identity("auditor")
|
|
if auditor == nil {
|
|
return "", errors.New("auditor identity not found")
|
|
}
|
|
tx, err := ttx.NewTransaction(context, nil, ttx.WithAuditor(auditor))
|
|
if err != nil {
|
|
return "", errors.Wrap(err, "failed creating transaction")
|
|
}
|
|
|
|
// The sender will select tokens owned by this wallet
|
|
logger.Debug("loading wallet [%s]", v.Wallet)
|
|
senderWallet := ttx.GetWallet(context, v.Wallet)
|
|
if senderWallet == nil {
|
|
return "", errors.Errorf("sender wallet [%s] not found", v.Wallet)
|
|
}
|
|
|
|
// the sender adds a new redeem operation to the transaction following the instruction received.
|
|
// Notice the use of `token2.WithTokenIDs(t.TokenIDs...)`. If t.TokenIDs is not empty, the Redeem
|
|
// function uses those tokens, otherwise the tokens will be selected on the spot.
|
|
// Token selection happens internally by invoking the default token selector:
|
|
// selector, err := tx.TokenService().SelectorManager().NewSelector(tx.ID())
|
|
// if err != nil {
|
|
// return "", errors.Wrap(err, "failed getting selector")
|
|
// }
|
|
// selector.Select(wallet, amount, tokenType)
|
|
// It is also possible to pass a custom token selector to the Redeem function by using the relative opt:
|
|
// token2.WithTokenSelector(selector).
|
|
err = tx.Redeem(
|
|
senderWallet,
|
|
v.TokenType,
|
|
v.Quantity,
|
|
token2.WithTokenIDs(v.TokenIDs...),
|
|
)
|
|
if err != nil {
|
|
return "", errors.Wrap(err, "failed adding tokens to redeem")
|
|
}
|
|
|
|
// You can set any metadata you want. It is shared with the
|
|
// auditor but not committed to the ledger.
|
|
if v.Message != "" {
|
|
tx.SetApplicationMetadata("message", []byte(v.Message))
|
|
}
|
|
|
|
// The sender is ready to collect all the required signatures.
|
|
// In this case, the sender's and the auditor's signatures.
|
|
// Invoke the Token Chaincode to collect endorsements on the Token Request and prepare the relative transaction.
|
|
// This is all done in one shot running the following view.
|
|
logger.Infof("collecting signatures and submitting transaction to chaincode: [%s]", tx.ID())
|
|
_, err = context.RunView(ttx.NewCollectEndorsementsView(tx))
|
|
if err != nil {
|
|
return "", errors.Wrap(err, "failed to sign transaction")
|
|
}
|
|
// Send to the ordering service and wait for finality
|
|
logger.Infof("submitting fabric transaction to orderer for final settlemement: [%s]", tx.ID())
|
|
_, err = context.RunView(ttx.NewOrderingAndFinalityView(tx))
|
|
if err != nil {
|
|
return "", errors.Wrap(err, "failed to order or commit transaction")
|
|
}
|
|
return tx.ID(), nil
|
|
}
|