fabric-samples/token-erc-1155/chaincode-go/chaincode/contract.go
Baran Kılıç 36fb3a9273
Add ERC1155 chaincode in Go (#463)
Signed-off-by: Baran Kılıç <baran.kilic@boun.edu.tr>
2021-09-01 23:05:57 +02:00

808 lines
26 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
2021 Baran Kılıç <baran.kilic@boun.edu.tr>
SPDX-License-Identifier: Apache-2.0
*/
package chaincode
import (
"encoding/json"
"fmt"
"strconv"
"strings"
"github.com/hyperledger/fabric-contract-api-go/contractapi"
)
const uriKey = "uri"
const balancePrefix = "account~tokenId~sender"
const approvalPrefix = "account~operator"
const minterMSPID = "Org1MSP"
// SmartContract provides functions for transferring tokens between accounts
type SmartContract struct {
contractapi.Contract
}
// TransferSingle MUST emit when a single token is transferred, including zero
// value transfers as well as minting or burning.
// The operator argument MUST be msg.sender.
// The from argument MUST be the address of the holder whose balance is decreased.
// The to argument MUST be the address of the recipient whose balance is increased.
// The id argument MUST be the token type being transferred.
// The value argument MUST be the number of tokens the holder balance is decreased
// by and match what the recipient balance is increased by.
// When minting/creating tokens, the from argument MUST be set to `0x0` (i.e. zero address).
// When burning/destroying tokens, the to argument MUST be set to `0x0` (i.e. zero address).
type TransferSingle struct {
Operator string `json:"operator"`
From string `json:"from"`
To string `json:"to"`
ID uint64 `json:"id"`
Value uint64 `json:"value"`
}
// TransferBatch MUST emit when tokens are transferred, including zero value
// transfers as well as minting or burning.
// The operator argument MUST be msg.sender.
// The from argument MUST be the address of the holder whose balance is decreased.
// The to argument MUST be the address of the recipient whose balance is increased.
// The ids argument MUST be the list of tokens being transferred.
// The values argument MUST be the list of number of tokens (matching the list
// and order of tokens specified in _ids) the holder balance is decreased by
// and match what the recipient balance is increased by.
// When minting/creating tokens, the from argument MUST be set to `0x0` (i.e. zero address).
// When burning/destroying tokens, the to argument MUST be set to `0x0` (i.e. zero address).
type TransferBatch struct {
Operator string `json:"operator"`
From string `json:"from"`
To string `json:"to"`
IDs []uint64 `json:"ids"`
Values []uint64 `json:"values"`
}
// TransferBatchMultiRecipient MUST emit when tokens are transferred, including zero value
// transfers as well as minting or burning.
// The operator argument MUST be msg.sender.
// The from argument MUST be the address of the holder whose balance is decreased.
// The to argument MUST be the list of the addresses of the recipients whose balance is increased.
// The ids argument MUST be the list of tokens being transferred.
// The values argument MUST be the list of number of tokens (matching the list
// and order of tokens specified in _ids) the holder balance is decreased by
// and match what the recipient balance is increased by.
// When minting/creating tokens, the from argument MUST be set to `0x0` (i.e. zero address).
// When burning/destroying tokens, the to argument MUST be set to `0x0` (i.e. zero address).
type TransferBatchMultiRecipient struct {
Operator string `json:"operator"`
From string `json:"from"`
To []string `json:"to"`
IDs []uint64 `json:"ids"`
Values []uint64 `json:"values"`
}
// ApprovalForAll MUST emit when approval for a second party/operator address
// to manage all tokens for an owner address is enabled or disabled
// (absence of an event assumes disabled).
type ApprovalForAll struct {
Owner string `json:"owner"`
Operator string `json:"operator"`
Approved bool `json:"approved"`
}
// URI MUST emit when the URI is updated for a token ID.
// Note: This event is not used in this contract implementation because in this implementation,
// only the programmatic way of setting URI is used. The URI should contain {id} as part of it
// and the clients MUST replace this with the actual token ID.
type URI struct {
Value string `json:"value"`
ID uint64 `json:"id"`
}
type ToID struct {
To string
ID uint64
}
// Mint creates amount tokens of token type id and assigns them to account.
// This function emits a TransferSingle event.
func (s *SmartContract) Mint(ctx contractapi.TransactionContextInterface, account string, id uint64, amount uint64) error {
// Check minter authorization - this sample assumes Org1 is the central banker with privilege to mint new tokens
err := authorizationHelper(ctx)
if err != nil {
return err
}
// Get ID of submitting client identity
operator, err := ctx.GetClientIdentity().GetID()
if err != nil {
return fmt.Errorf("failed to get client id: %v", err)
}
// Mint tokens
err = mintHelper(ctx, operator, account, id, amount)
if err != nil {
return err
}
// Emit TransferSingle event
transferSingleEvent := TransferSingle{operator, "0x0", account, id, amount}
return emitTransferSingle(ctx, transferSingleEvent)
}
// MintBatch creates amount tokens for each token type id and assigns them to account.
// This function emits a TransferBatch event.
func (s *SmartContract) MintBatch(ctx contractapi.TransactionContextInterface, account string, ids []uint64, amounts []uint64) error {
if len(ids) != len(amounts) {
return fmt.Errorf("ids and amounts must have the same length")
}
// Check minter authorization - this sample assumes Org1 is the central banker with privilege to mint new tokens
err := authorizationHelper(ctx)
if err != nil {
return err
}
// Get ID of submitting client identity
operator, err := ctx.GetClientIdentity().GetID()
if err != nil {
return fmt.Errorf("failed to get client id: %v", err)
}
// Group amount by token id because we can only send token to a recipient only one time in a block. This prevents key conflicts
amountToSend := make(map[uint64]uint64) // token id => amount
for i := 0; i < len(amounts); i++ {
amountToSend[ids[i]] += amounts[i]
}
// Mint tokens
for id, amount := range amountToSend {
err = mintHelper(ctx, operator, account, id, amount)
if err != nil {
return err
}
}
// Emit TransferBatch event
transferBatchEvent := TransferBatch{operator, "0x0", account, ids, amounts}
return emitTransferBatch(ctx, transferBatchEvent)
}
// Burn destroys amount tokens of token type id from account.
// This function triggers a TransferSingle event.
func (s *SmartContract) Burn(ctx contractapi.TransactionContextInterface, account string, id uint64, amount uint64) error {
if account == "0x0" {
return fmt.Errorf("burn to the zero address")
}
// Check minter authorization - this sample assumes Org1 is the central banker with privilege to burn new tokens
err := authorizationHelper(ctx)
if err != nil {
return err
}
// Get ID of submitting client identity
operator, err := ctx.GetClientIdentity().GetID()
if err != nil {
return fmt.Errorf("failed to get client id: %v", err)
}
// Burn tokens
err = removeBalance(ctx, account, []uint64{id}, []uint64{amount})
if err != nil {
return err
}
transferSingleEvent := TransferSingle{operator, account, "0x0", id, amount}
return emitTransferSingle(ctx, transferSingleEvent)
}
// BurnBatch destroys amount tokens of for each token type id from account.
// This function emits a TransferBatch event.
func (s *SmartContract) BurnBatch(ctx contractapi.TransactionContextInterface, account string, ids []uint64, amounts []uint64) error {
if account == "0x0" {
return fmt.Errorf("burn to the zero address")
}
if len(ids) != len(amounts) {
return fmt.Errorf("ids and amounts must have the same length")
}
// Check minter authorization - this sample assumes Org1 is the central banker with privilege to burn new tokens
err := authorizationHelper(ctx)
if err != nil {
return err
}
// Get ID of submitting client identity
operator, err := ctx.GetClientIdentity().GetID()
if err != nil {
return fmt.Errorf("failed to get client id: %v", err)
}
err = removeBalance(ctx, account, ids, amounts)
if err != nil {
return err
}
transferBatchEvent := TransferBatch{operator, account, "0x0", ids, amounts}
return emitTransferBatch(ctx, transferBatchEvent)
}
// TransferFrom transfers tokens from sender account to recipient account
// recipient account must be a valid clientID as returned by the ClientID() function
// This function triggers a TransferSingle event
func (s *SmartContract) TransferFrom(ctx contractapi.TransactionContextInterface, sender string, recipient string, id uint64, amount uint64) error {
if sender == recipient {
return fmt.Errorf("transfer to self")
}
// Get ID of submitting client identity
operator, err := ctx.GetClientIdentity().GetID()
if err != nil {
return fmt.Errorf("failed to get client id: %v", err)
}
// Check whether operator is owner or approved
if operator != sender {
approved, err := _isApprovedForAll(ctx, sender, operator)
if err != nil {
return err
}
if !approved {
return fmt.Errorf("caller is not owner nor is approved")
}
}
// Withdraw the funds from the sender address
err = removeBalance(ctx, sender, []uint64{id}, []uint64{amount})
if err != nil {
return err
}
if recipient == "0x0" {
return fmt.Errorf("transfer to the zero address")
}
// Deposit the fund to the recipient address
err = addBalance(ctx, sender, recipient, id, amount)
if err != nil {
return err
}
// Emit TransferSingle event
transferSingleEvent := TransferSingle{operator, sender, recipient, id, amount}
return emitTransferSingle(ctx, transferSingleEvent)
}
// BatchTransferFrom transfers multiple tokens from sender account to recipient account
// recipient account must be a valid clientID as returned by the ClientID() function
// This function triggers a TransferBatch event
func (s *SmartContract) BatchTransferFrom(ctx contractapi.TransactionContextInterface, sender string, recipient string, ids []uint64, amounts []uint64) error {
if sender == recipient {
return fmt.Errorf("transfer to self")
}
if len(ids) != len(amounts) {
return fmt.Errorf("ids and amounts must have the same length")
}
// Get ID of submitting client identity
operator, err := ctx.GetClientIdentity().GetID()
if err != nil {
return fmt.Errorf("failed to get client id: %v", err)
}
// Check whether operator is owner or approved
if operator != sender {
approved, err := _isApprovedForAll(ctx, sender, operator)
if err != nil {
return err
}
if !approved {
return fmt.Errorf("caller is not owner nor is approved")
}
}
// Withdraw the funds from the sender address
err = removeBalance(ctx, sender, ids, amounts)
if err != nil {
return err
}
if recipient == "0x0" {
return fmt.Errorf("transfer to the zero address")
}
// Group amount by token id because we can only send token to a recipient only one time in a block. This prevents key conflicts
amountToSend := make(map[uint64]uint64) // token id => amount
for i := 0; i < len(amounts); i++ {
amountToSend[ids[i]] += amounts[i]
}
// Deposit the funds to the recipient address
for id, amount := range amountToSend {
err = addBalance(ctx, sender, recipient, id, amount)
if err != nil {
return err
}
}
transferBatchEvent := TransferBatch{operator, sender, recipient, ids, amounts}
return emitTransferBatch(ctx, transferBatchEvent)
}
// BatchTransferFromMultiRecipient transfers multiple tokens from sender account to multiple recipient accounts
// recipient account must be a valid clientID as returned by the ClientID() function
// This function triggers a TransferBatchMultiRecipient event
func (s *SmartContract) BatchTransferFromMultiRecipient(ctx contractapi.TransactionContextInterface, sender string, recipients []string, ids []uint64, amounts []uint64) error {
if len(recipients) != len(ids) || len(ids) != len(amounts) {
return fmt.Errorf("recipients, ids, and amounts must have the same length")
}
for _, recipient := range recipients {
if sender == recipient {
return fmt.Errorf("transfer to self")
}
}
// Get ID of submitting client identity
operator, err := ctx.GetClientIdentity().GetID()
if err != nil {
return fmt.Errorf("failed to get client id: %v", err)
}
// Check whether operator is owner or approved
if operator != sender {
approved, err := _isApprovedForAll(ctx, sender, operator)
if err != nil {
return err
}
if !approved {
return fmt.Errorf("caller is not owner nor is approved")
}
}
// Withdraw the funds from the sender address
err = removeBalance(ctx, sender, ids, amounts)
if err != nil {
return err
}
// Group amount by (recipient, id ) pair because we can only send token to a recipient only one time in a block. This prevents key conflicts
amountToSend := make(map[ToID]uint64) // (recipient, id ) => amount
for i := 0; i < len(amounts); i++ {
amountToSend[ToID{recipients[i], ids[i]}] += amounts[i]
}
// Deposit the funds to the recipient addresses
for key, amount := range amountToSend {
if key.To == "0x0" {
return fmt.Errorf("transfer to the zero address")
}
err = addBalance(ctx, sender, key.To, key.ID, amount)
if err != nil {
return err
}
}
// Emit TransferBatchMultiRecipient event
transferBatchMultiRecipientEvent := TransferBatchMultiRecipient{operator, sender, recipients, ids, amounts}
return emitTransferBatchMultiRecipient(ctx, transferBatchMultiRecipientEvent)
}
// IsApprovedForAll returns true if operator is approved to transfer account's tokens.
func (s *SmartContract) IsApprovedForAll(ctx contractapi.TransactionContextInterface, account string, operator string) (bool, error) {
return _isApprovedForAll(ctx, account, operator)
}
// _isApprovedForAll returns true if operator is approved to transfer account's tokens.
func _isApprovedForAll(ctx contractapi.TransactionContextInterface, account string, operator string) (bool, error) {
approvalKey, err := ctx.GetStub().CreateCompositeKey(approvalPrefix, []string{account, operator})
if err != nil {
return false, fmt.Errorf("failed to create the composite key for prefix %s: %v", approvalPrefix, err)
}
approvalBytes, err := ctx.GetStub().GetState(approvalKey)
if err != nil {
return false, fmt.Errorf("failed to read approval of operator %s for account %s from world state: %v", operator, account, err)
}
if approvalBytes == nil {
return false, nil
}
var approved bool
err = json.Unmarshal(approvalBytes, &approved)
if err != nil {
return false, fmt.Errorf("failed to decode approval JSON of operator %s for account %s: %v", operator, account, err)
}
return approved, nil
}
// SetApprovalForAll returns true if operator is approved to transfer account's tokens.
func (s *SmartContract) SetApprovalForAll(ctx contractapi.TransactionContextInterface, operator string, approved bool) error {
// Get ID of submitting client identity
account, err := ctx.GetClientIdentity().GetID()
if err != nil {
return fmt.Errorf("failed to get client id: %v", err)
}
if account == operator {
return fmt.Errorf("setting approval status for self")
}
approvalForAllEvent := ApprovalForAll{account, operator, approved}
approvalForAllEventJSON, err := json.Marshal(approvalForAllEvent)
if err != nil {
return fmt.Errorf("failed to obtain JSON encoding: %v", err)
}
err = ctx.GetStub().SetEvent("ApprovalForAll", approvalForAllEventJSON)
if err != nil {
return fmt.Errorf("failed to set event: %v", err)
}
approvalKey, err := ctx.GetStub().CreateCompositeKey(approvalPrefix, []string{account, operator})
if err != nil {
return fmt.Errorf("failed to create the composite key for prefix %s: %v", approvalPrefix, err)
}
approvalJSON, err := json.Marshal(approved)
if err != nil {
return fmt.Errorf("failed to encode approval JSON of operator %s for account %s: %v", operator, account, err)
}
err = ctx.GetStub().PutState(approvalKey, approvalJSON)
if err != nil {
return err
}
return nil
}
// BalanceOf returns the balance of the given account
func (s *SmartContract) BalanceOf(ctx contractapi.TransactionContextInterface, account string, id uint64) (uint64, error) {
return balanceOfHelper(ctx, account, id)
}
// BalanceOfBatch returns the balance of multiple account/token pairs
func (s *SmartContract) BalanceOfBatch(ctx contractapi.TransactionContextInterface, accounts []string, ids []uint64) ([]uint64, error) {
if len(accounts) != len(ids) {
return nil, fmt.Errorf("accounts and ids must have the same length")
}
balances := make([]uint64, len(accounts))
for i := 0; i < len(accounts); i++ {
var err error
balances[i], err = balanceOfHelper(ctx, accounts[i], ids[i])
if err != nil {
return nil, err
}
}
return balances, nil
}
// ClientAccountBalance returns the balance of the requesting client's account
func (s *SmartContract) ClientAccountBalance(ctx contractapi.TransactionContextInterface, id uint64) (uint64, error) {
// Get ID of submitting client identity
clientID, err := ctx.GetClientIdentity().GetID()
if err != nil {
return 0, fmt.Errorf("failed to get client id: %v", err)
}
return balanceOfHelper(ctx, clientID, id)
}
// ClientAccountID returns the id of the requesting client's account
// In this implementation, the client account ID is the clientId itself
// Users can use this function to get their own account id, which they can then give to others as the payment address
func (s *SmartContract) ClientAccountID(ctx contractapi.TransactionContextInterface) (string, error) {
// Get ID of submitting client identity
clientAccountID, err := ctx.GetClientIdentity().GetID()
if err != nil {
return "", fmt.Errorf("failed to get client id: %v", err)
}
return clientAccountID, nil
}
// SetURI set the URI value
// This function triggers URI event for each token id
func (s *SmartContract) SetURI(ctx contractapi.TransactionContextInterface, uri string) error {
// Check minter authorization - this sample assumes Org1 is the central banker with privilege to mint new tokens
err := authorizationHelper(ctx)
if err != nil {
return err
}
if !strings.Contains(uri, "{id}") {
return fmt.Errorf("failed to set uri, uri should contain '{id}'")
}
err = ctx.GetStub().PutState(uriKey, []byte(uri))
if err != nil {
return fmt.Errorf("failed to set uri: %v", err)
}
return nil
}
// URI returns the URI
func (s *SmartContract) URI(ctx contractapi.TransactionContextInterface, id uint64) (string, error) {
uriBytes, err := ctx.GetStub().GetState(uriKey)
if err != nil {
return "", fmt.Errorf("failed to get uri: %v", err)
}
if uriBytes == nil {
return "", fmt.Errorf("no uri is set: %v", err)
}
return string(uriBytes), nil
}
func (s *SmartContract) BroadcastTokenExistance(ctx contractapi.TransactionContextInterface, id uint64) error {
// Check minter authorization - this sample assumes Org1 is the central banker with privilege to mint new tokens
err := authorizationHelper(ctx)
if err != nil {
return err
}
// Get ID of submitting client identity
operator, err := ctx.GetClientIdentity().GetID()
if err != nil {
return fmt.Errorf("failed to get client id: %v", err)
}
// Emit TransferSingle event
transferSingleEvent := TransferSingle{operator, "0x0", "0x0", id, 0}
return emitTransferSingle(ctx, transferSingleEvent)
}
// Helper Functions
// authorizationHelper checks minter authorization - this sample assumes Org1 is the central banker with privilege to mint new tokens
func authorizationHelper(ctx contractapi.TransactionContextInterface) error {
clientMSPID, err := ctx.GetClientIdentity().GetMSPID()
if err != nil {
return fmt.Errorf("failed to get MSPID: %v", err)
}
if clientMSPID != minterMSPID {
return fmt.Errorf("client is not authorized to mint new tokens")
}
return nil
}
func mintHelper(ctx contractapi.TransactionContextInterface, operator string, account string, id uint64, amount uint64) error {
if account == "0x0" {
return fmt.Errorf("mint to the zero address")
}
if amount <= 0 {
return fmt.Errorf("mint amount must be a positive integer")
}
err := addBalance(ctx, operator, account, id, amount)
if err != nil {
return err
}
return nil
}
func addBalance(ctx contractapi.TransactionContextInterface, sender string, recipient string, id uint64, amount uint64) error {
// Convert id to string
idString := strconv.FormatUint(uint64(id), 10)
balanceKey, err := ctx.GetStub().CreateCompositeKey(balancePrefix, []string{recipient, idString, sender})
if err != nil {
return fmt.Errorf("failed to create the composite key for prefix %s: %v", balancePrefix, err)
}
balanceBytes, err := ctx.GetStub().GetState(balanceKey)
if err != nil {
return fmt.Errorf("failed to read account %s from world state: %v", recipient, err)
}
var balance uint64 = 0
if balanceBytes != nil {
balance, _ = strconv.ParseUint(string(balanceBytes), 10, 64)
}
balance += amount
err = ctx.GetStub().PutState(balanceKey, []byte(strconv.FormatUint(uint64(balance), 10)))
if err != nil {
return err
}
return nil
}
func setBalance(ctx contractapi.TransactionContextInterface, sender string, recipient string, id uint64, amount uint64) error {
// Convert id to string
idString := strconv.FormatUint(uint64(id), 10)
balanceKey, err := ctx.GetStub().CreateCompositeKey(balancePrefix, []string{recipient, idString, sender})
if err != nil {
return fmt.Errorf("failed to create the composite key for prefix %s: %v", balancePrefix, err)
}
err = ctx.GetStub().PutState(balanceKey, []byte(strconv.FormatUint(uint64(amount), 10)))
if err != nil {
return err
}
return nil
}
func removeBalance(ctx contractapi.TransactionContextInterface, sender string, ids []uint64, amounts []uint64) error {
// Calculate the total amount of each token to withdraw
necessaryFunds := make(map[uint64]uint64) // token id -> necessary amount
for i := 0; i < len(amounts); i++ {
necessaryFunds[ids[i]] += amounts[i]
}
// Check whether the sender has the necessary funds and withdraw them from the account
for tokenId, neededAmount := range necessaryFunds {
idString := strconv.FormatUint(uint64(tokenId), 10)
var partialBalance uint64
var selfRecipientKeyNeedsToBeRemoved bool
var selfRecipientKey string
balanceIterator, err := ctx.GetStub().GetStateByPartialCompositeKey(balancePrefix, []string{sender, idString})
if err != nil {
return fmt.Errorf("failed to get state for prefix %v: %v", balancePrefix, err)
}
defer balanceIterator.Close()
// Iterate over keys that store balances and add them to partialBalance until
// either the necessary amount is reached or the keys ended
for balanceIterator.HasNext() && partialBalance < neededAmount {
queryResponse, err := balanceIterator.Next()
if err != nil {
return fmt.Errorf("failed to get the next state for prefix %v: %v", balancePrefix, err)
}
partBalAmount, _ := strconv.ParseUint(string(queryResponse.Value), 10, 64)
partialBalance += partBalAmount
_, compositeKeyParts, err := ctx.GetStub().SplitCompositeKey(queryResponse.Key)
if err != nil {
return err
}
if compositeKeyParts[2] == sender {
selfRecipientKeyNeedsToBeRemoved = true
selfRecipientKey = queryResponse.Key
} else {
err = ctx.GetStub().DelState(queryResponse.Key)
if err != nil {
return fmt.Errorf("failed to delete the state of %v: %v", queryResponse.Key, err)
}
}
}
if partialBalance < neededAmount {
return fmt.Errorf("sender has insufficient funds for token %v, needed funds: %v, available fund: %v", tokenId, neededAmount, partialBalance)
} else if partialBalance > neededAmount {
// Send the remainder back to the sender
remainder := partialBalance - neededAmount
if selfRecipientKeyNeedsToBeRemoved {
// Set balance for the key that has the same address for sender and recipient
err = setBalance(ctx, sender, sender, tokenId, remainder)
if err != nil {
return err
}
} else {
err = addBalance(ctx, sender, sender, tokenId, remainder)
if err != nil {
return err
}
}
} else {
// Delete self recipient key
err = ctx.GetStub().DelState(selfRecipientKey)
if err != nil {
return fmt.Errorf("failed to delete the state of %v: %v", selfRecipientKey, err)
}
}
}
return nil
}
func emitTransferSingle(ctx contractapi.TransactionContextInterface, transferSingleEvent TransferSingle) error {
transferSingleEventJSON, err := json.Marshal(transferSingleEvent)
if err != nil {
return fmt.Errorf("failed to obtain JSON encoding: %v", err)
}
err = ctx.GetStub().SetEvent("TransferSingle", transferSingleEventJSON)
if err != nil {
return fmt.Errorf("failed to set event: %v", err)
}
return nil
}
func emitTransferBatch(ctx contractapi.TransactionContextInterface, transferBatchEvent TransferBatch) error {
transferBatchEventJSON, err := json.Marshal(transferBatchEvent)
if err != nil {
return fmt.Errorf("failed to obtain JSON encoding: %v", err)
}
err = ctx.GetStub().SetEvent("TransferBatch", transferBatchEventJSON)
if err != nil {
return fmt.Errorf("failed to set event: %v", err)
}
return nil
}
func emitTransferBatchMultiRecipient(ctx contractapi.TransactionContextInterface, transferBatchMultiRecipientEvent TransferBatchMultiRecipient) error {
transferBatchMultiRecipientEventJSON, err := json.Marshal(transferBatchMultiRecipientEvent)
if err != nil {
return fmt.Errorf("failed to obtain JSON encoding: %v", err)
}
err = ctx.GetStub().SetEvent("TransferBatchMultiRecipient", transferBatchMultiRecipientEventJSON)
if err != nil {
return fmt.Errorf("failed to set event: %v", err)
}
return nil
}
// balanceOfHelper returns the balance of the given account
func balanceOfHelper(ctx contractapi.TransactionContextInterface, account string, id uint64) (uint64, error) {
if account == "0x0" {
return 0, fmt.Errorf("balance query for the zero address")
}
// Convert id to string
idString := strconv.FormatUint(uint64(id), 10)
var balance uint64
balanceIterator, err := ctx.GetStub().GetStateByPartialCompositeKey(balancePrefix, []string{account, idString})
if err != nil {
return 0, fmt.Errorf("failed to get state for prefix %v: %v", balancePrefix, err)
}
defer balanceIterator.Close()
for balanceIterator.HasNext() {
queryResponse, err := balanceIterator.Next()
if err != nil {
return 0, fmt.Errorf("failed to get the next state for prefix %v: %v", balancePrefix, err)
}
balAmount, _ := strconv.ParseUint(string(queryResponse.Value), 10, 64)
balance += balAmount
}
return balance, nil
}