Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
module github.com/hyperledger/fabric-samples/full-stack-asset-transfer-guide/contracts/asset-transfer-go

go 1.23

require (
github.com/hyperledger/fabric-chaincode-go/v2 v2.0.0
github.com/hyperledger/fabric-contract-api-go/v2 v2.2.0
)

require (
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/spec v0.21.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.4 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
golang.org/x/net v0.28.0 // indirect
golang.org/x/sys v0.24.0 // indirect
golang.org/x/text v0.17.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect
google.golang.org/grpc v1.67.0 // indirect
google.golang.org/protobuf v1.36.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/hyperledger/fabric-chaincode-go/v2 v2.0.0 h1:IhkHfrl5X/fVnmB6pWeCYCdIJRi9bxj+WTnVN8DtW3c=
github.com/hyperledger/fabric-chaincode-go/v2 v2.0.0/go.mod h1:PHHaFffjw7p7n9bmCfcm7RqDqYdivNEsJdiNIKZo5Lk=
github.com/hyperledger/fabric-contract-api-go/v2 v2.2.0 h1:rmUoBmciB0GL/miqcbJmJbgp5QTWoJUrZo+CNxrNLF4=
github.com/hyperledger/fabric-contract-api-go/v2 v2.2.0/go.mod h1:FeWeO/jwGjiME7ak3GufqKIcwkejtzrDG4QxbfKydWs=
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.4 h1:YJrd+gMaeY0/vsN0aS0QkEKTivGoUnSRIXxGJ7KI+Pc=
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.4/go.mod h1:bau/6AJhvEcu9GKKYHlDXAxXKzYNfhP6xu2GXuxEcFk=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw=
google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
SPDX-License-Identifier: Apache-2.0
*/

package main

import (
"log"

"github.com/hyperledger/fabric-contract-api-go/v2/contractapi"
asset "github.com/hyperledger/fabric-samples/full-stack-asset-transfer-guide/contracts/asset-transfer-go/smartcontract"
)

func main() {
assetChaincode, err := contractapi.NewChaincode(&asset.SmartContract{})
if err != nil {
log.Panicf("Error creating asset-transfer-go chaincode: %v", err)
}

if err := assetChaincode.Start(); err != nil {
log.Panicf("Error starting asset-transfer-go chaincode: %v", err)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
SPDX-License-Identifier: Apache-2.0
*/

package smartcontract

// Asset represents a tradeable asset in the world state.
type Asset struct {
ID string `json:"ID"`
Color string `json:"Color"`
Owner string `json:"Owner"`
AppraisedValue int `json:"AppraisedValue"`
Size int `json:"Size"`
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
/*
SPDX-License-Identifier: Apache-2.0
*/

package smartcontract

import (
"encoding/json"
"fmt"
"log"

"github.com/hyperledger/fabric-contract-api-go/v2/contractapi"
)

type SmartContract struct {
contractapi.Contract
}

func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface, asset *Asset) error {
if asset == nil {
return fmt.Errorf("asset cannot be nil")
}

if asset.ID == "" {
return fmt.Errorf("Missing ID")
}

ownerIdentifier, err := clientIdentifierWithUser(ctx, asset.Owner)
if err != nil {
return err
}

asset.Owner, err = marshalOwnerIdentifier(ownerIdentifier)
if err != nil {
return err
}

exists, err := s.AssetExists(ctx, asset.ID)
if err != nil {
return err
}
if exists {
return fmt.Errorf("The asset %s already exists", asset.ID)
}

assetBytes, err := json.Marshal(asset)
if err != nil {
return fmt.Errorf("failed to marshal asset: %v", err)
}

if err := ctx.GetStub().PutState(asset.ID, assetBytes); err != nil {
return fmt.Errorf("failed to put asset in world state: %v", err)
}

clientMSP, err := ctx.GetClientIdentity().GetMSPID()
if err != nil {
return fmt.Errorf("failed to get client MSP ID: %v", err)
}

if err := setStateBasedEndorsement(ctx, asset.ID, clientMSP); err != nil {
return err
}

if err := ctx.GetStub().SetEvent("CreateAsset", assetBytes); err != nil {
return fmt.Errorf("failed to set event CreateAsset: %v", err)
}

return nil
}

func (s *SmartContract) ReadAsset(ctx contractapi.TransactionContextInterface, id string) (*Asset, error) {
assetBytes, err := s.readAsset(ctx, id)
if err != nil {
return nil, err
}

var asset Asset
if err := json.Unmarshal(assetBytes, &asset); err != nil {
return nil, fmt.Errorf("failed to unmarshal asset: %v", err)
}

return &asset, nil
}

func (s *SmartContract) UpdateAsset(ctx contractapi.TransactionContextInterface, assetUpdate *Asset) error {
if assetUpdate == nil {
return fmt.Errorf("asset cannot be nil")
}
if assetUpdate.ID == "" {
return fmt.Errorf("No asset ID specified")
}

existingAsset, err := s.ReadAsset(ctx, assetUpdate.ID)
if err != nil {
return err
}

allowed, err := hasWritePermission(ctx, existingAsset)
if err != nil {
return err
}
if !allowed {
return fmt.Errorf("Only owner can update assets")
}

updatedAsset := *existingAsset
if assetUpdate.Color != "" {
updatedAsset.Color = assetUpdate.Color
}
if assetUpdate.Size != 0 {
updatedAsset.Size = assetUpdate.Size
}
if assetUpdate.AppraisedValue != 0 {
updatedAsset.AppraisedValue = assetUpdate.AppraisedValue
}

assetBytes, err := json.Marshal(&updatedAsset)
if err != nil {
return fmt.Errorf("failed to marshal updated asset: %v", err)
}

if err := ctx.GetStub().PutState(updatedAsset.ID, assetBytes); err != nil {
return fmt.Errorf("failed to put asset in world state: %v", err)
}

clientMSP, err := ctx.GetClientIdentity().GetMSPID()
if err != nil {
return fmt.Errorf("failed to get client MSP ID: %v", err)
}

if err := setStateBasedEndorsement(ctx, updatedAsset.ID, clientMSP); err != nil {
return err
}

if err := ctx.GetStub().SetEvent("UpdateAsset", assetBytes); err != nil {
return fmt.Errorf("failed to set event UpdateAsset: %v", err)
}

return nil
}

func (s *SmartContract) DeleteAsset(ctx contractapi.TransactionContextInterface, id string) error {
asset, err := s.ReadAsset(ctx, id)
if err != nil {
return err
}

allowed, err := hasWritePermission(ctx, asset)
if err != nil {
return err
}
if !allowed {
return fmt.Errorf("Only owner can delete assets")
}

if err := ctx.GetStub().DelState(id); err != nil {
return fmt.Errorf("failed to delete asset from world state: %v", err)
}

assetBytes, err := json.Marshal(asset)
if err != nil {
return fmt.Errorf("failed to marshal asset event payload: %v", err)
}

if err := ctx.GetStub().SetEvent("DeleteAsset", assetBytes); err != nil {
return fmt.Errorf("failed to set event DeleteAsset: %v", err)
}

return nil
}

func (s *SmartContract) AssetExists(ctx contractapi.TransactionContextInterface, id string) (bool, error) {
assetBytes, err := ctx.GetStub().GetState(id)
if err != nil {
return false, fmt.Errorf("failed to read from world state: %v", err)
}

return len(assetBytes) > 0, nil
}

func (s *SmartContract) TransferAsset(ctx contractapi.TransactionContextInterface, id string, newOwner string, newOwnerOrg string) error {
asset, err := s.ReadAsset(ctx, id)
if err != nil {
return err
}

allowed, err := hasWritePermission(ctx, asset)
if err != nil {
return err
}
if !allowed {
return fmt.Errorf("Only owner can transfer assets")
}

asset.Owner, err = marshalOwnerIdentifier(ownerIdentifier(newOwner, newOwnerOrg))
if err != nil {
return err
}

assetBytes, err := json.Marshal(asset)
if err != nil {
return fmt.Errorf("failed to marshal transferred asset: %v", err)
}

if err := ctx.GetStub().PutState(id, assetBytes); err != nil {
return fmt.Errorf("failed to put asset in world state: %v", err)
}

if err := setStateBasedEndorsement(ctx, id, newOwnerOrg); err != nil {
return err
}

if err := ctx.GetStub().SetEvent("TransferAsset", assetBytes); err != nil {
return fmt.Errorf("failed to set event TransferAsset: %v", err)
}

return nil
}

func (s *SmartContract) GetAllAssets(ctx contractapi.TransactionContextInterface) ([]*Asset, error) {
iterator, err := ctx.GetStub().GetStateByRange("", "")
if err != nil {
return nil, fmt.Errorf("failed to retrieve all assets: %v", err)
}
defer func() {
_ = iterator.Close()
}()

assets := make([]*Asset, 0)
for iterator.HasNext() {
queryResponse, err := iterator.Next()
if err != nil {
return nil, fmt.Errorf("failed retrieving next item: %v", err)
}

var asset Asset
if err := json.Unmarshal(queryResponse.Value, &asset); err != nil {
log.Printf("failed to unmarshal asset from world state: %v", err)
continue
}
assets = append(assets, &asset)
}

return assets, nil
}

func (s *SmartContract) readAsset(ctx contractapi.TransactionContextInterface, id string) ([]byte, error) {
assetBytes, err := ctx.GetStub().GetState(id)
if err != nil {
return nil, fmt.Errorf("failed to read from world state: %v", err)
}

if len(assetBytes) == 0 {
return nil, fmt.Errorf("Sorry, asset %s has not been created", id)
}

return assetBytes, nil
}
Loading