mirror of
https://github.com/hyperledger/fabric-samples.git
synced 2026-06-18 16:05:10 +00:00
Add UTXO token sample (go chaincode) (#319)
Add a UTXO token sample go chaincode, as well as an associated readme tutorial. Signed-off-by: David Enyeart <enyeart@us.ibm.com>
This commit is contained in:
parent
fa0529c393
commit
882218af0f
5 changed files with 602 additions and 0 deletions
212
token-utxo/README.md
Normal file
212
token-utxo/README.md
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
# UTXO token scenario
|
||||
|
||||
The UTXO token smart contract demonstrates how to create and transfer fungible tokens using a UTXO (unspent transaction output) model. In a UTXO model, unspent transaction outputs representing a number of tokens can be 'spent' to transfer tokens between participants.
|
||||
An unspent transaction output can only be spent once, and the full value must be completely spent. A transaction that spends UTXOs as input will also generate new UTXOs as outputs, where the value of the inputs must equal the value of the outputs. As an example, if you own an unspent transaction output representing 5000 tokens, and you need to transfer 100 tokens to a recipient, the transaction would spend the 5000 token UTXO as input, create a new 100 token UTXO output owned by the recipient, and return a new 4900 token UTXO to you as 'change'.
|
||||
|
||||
Each UTXO in this sample has a key derived from the transaction id that created it, as well as a number of tokens, and an owner that is authorized to spend the tokens.
|
||||
Ownership of each UTXO could be represented at the organization level or client identity level. In this sample UTXO ownership is based on a client identity, where the client ID is simply a base64-encoded concatenation of the issuer and subject from the client identity's enrollment certificate. The client ID can therefore be used as the payment address when transferring tokens in a UTXO transaction.
|
||||
|
||||
While a transfer transaction spends UTXOs and creates new UTXOs for the recipient(s), a mint transaction can create new UTXOs. In this sample it is assumed that only one organization (played by Org1) is in a central banker role and can mint new tokens owned by their client ID. Any client from any organization can transfer tokens in a UTXO transaction.
|
||||
|
||||
In this tutorial, you will mint and transfer tokens as follows:
|
||||
|
||||
- A member of Org1 uses the `Mint` function to create a UTXO representing a number of tokens. The `Mint` function reads the certificate information of the client identity that submitted the transaction using the `GetClientIdentity.GetID()` API and assigns the UTXO ownership to the minter client ID.
|
||||
- The same minter client will then use the `Transfer` function to transfer the requested number of tokens to a recipient. The minted UTXO key gets passed as input to the `Transfer` function, a UTXO output representing the number of transferred tokens gets created for the recipient, and another UTXO output representing the 'change' gets created for the minter. It is assumed that the recipient has provided their client ID to the transfer caller out of band. The recipient can then transfer tokens to other registered users in the same fashion.
|
||||
|
||||
## Bring up the test network
|
||||
|
||||
You can run the UTXO token transfer scenario using the Fabric test network. Open a command terminal and navigate to the test network directory in your local clone of the `fabric-samples`. We will operate from the `test-network` directory for the remainder of the tutorial.
|
||||
```
|
||||
cd fabric-samples/test-network
|
||||
```
|
||||
|
||||
Run the following command to start the test network:
|
||||
```
|
||||
./network.sh up createChannel -ca
|
||||
```
|
||||
|
||||
The test network is deployed with two peer organizations. The `createChannel` flag deploys the network with a single channel named `mychannel` with Org1 and Org2 as channel members.
|
||||
The -ca flag is used to deploy the network using certificate authorities. This allows you to use each organization's CA to register and enroll new users for this tutorial.
|
||||
|
||||
## Deploy the smart contract to the channel
|
||||
|
||||
You can use the test network script to deploy the UTXO token contract to the channel that was just created. Deploy the smart contract to `mychannel` using the following command:
|
||||
```
|
||||
./network.sh deployCC -ccn token_utxo -ccp ../token-utxo/chaincode-go/
|
||||
```
|
||||
|
||||
The above command deploys the go chaincode with short name `token_utxo`. The smart contract will utilize the default endorsement policy of majority of channel members.
|
||||
Since the channel has two members, this implies that we'll need to get peer endorsements from 2 out of the 2 channel members.
|
||||
|
||||
Now you are ready to call the deployed smart contract via peer CLI calls. But let's first create the client identities for our scenario.
|
||||
|
||||
## Register identities
|
||||
|
||||
The smart contract supports UTXO ownership based on individual client identities from organizations that are members of the channel. In our scenario, the minter of the tokens will be a member of Org1, while the recipient will belong to Org2. To highlight the connection between the `GetClientIdentity().GetID()` API and the information within a user's certificate, we will register two new identities using the Org1 and Org2 Certificate Authorities (CA's), and then use the CA's to generate each identity's certificate and private key.
|
||||
|
||||
First, we need to set the following environment variables to use the the Fabric CA client (and subsequent commands)
|
||||
```
|
||||
export PATH=${PWD}/../bin:${PWD}:$PATH
|
||||
export FABRIC_CFG_PATH=$PWD/../config/
|
||||
```
|
||||
|
||||
The terminal we have been using will represent Org1. We will use the Org1 CA to create the minter identity. Set the Fabric CA client home to the MSP of the Org1 CA admin (this identity was generated by the test network script):
|
||||
```
|
||||
export FABRIC_CA_CLIENT_HOME=${PWD}/organizations/peerOrganizations/org1.example.com/
|
||||
```
|
||||
|
||||
You can register a new minter client identity using the `fabric-ca-client` tool:
|
||||
```
|
||||
fabric-ca-client register --caname ca-org1 --id.name minter --id.secret minterpw --id.type client --tls.certfiles ${PWD}/organizations/fabric-ca/org1/tls-cert.pem
|
||||
```
|
||||
|
||||
You can now generate the identity certificates and MSP folder by providing the enroll name and secret to the enroll command:
|
||||
```
|
||||
fabric-ca-client enroll -u https://minter:minterpw@localhost:7054 --caname ca-org1 -M ${PWD}/organizations/peerOrganizations/org1.example.com/users/minter@org1.example.com/msp --tls.certfiles ${PWD}/organizations/fabric-ca/org1/tls-cert.pem
|
||||
```
|
||||
|
||||
Run the command below to copy the Node OU configuration file into the minter identity MSP folder.
|
||||
```
|
||||
cp ${PWD}/organizations/peerOrganizations/org1.example.com/msp/config.yaml ${PWD}/organizations/peerOrganizations/org1.example.com/users/minter@org1.example.com/msp/config.yaml
|
||||
```
|
||||
|
||||
Open a new terminal to represent Org2 and navigate to /fabric-samples/test-network. We'll use the Org2 CA to create the Org2 recipient identity. Set the Fabric CA client home to the MSP of the Org2 CA admin:
|
||||
```
|
||||
cd fabric-samples/test-network
|
||||
export FABRIC_CA_CLIENT_HOME=${PWD}/organizations/peerOrganizations/org2.example.com/
|
||||
```
|
||||
|
||||
You can register a recipient client identity using the `fabric-ca-client` tool:
|
||||
```
|
||||
fabric-ca-client register --caname ca-org2 --id.name recipient --id.secret recipientpw --id.type client --tls.certfiles ${PWD}/organizations/fabric-ca/org2/tls-cert.pem
|
||||
```
|
||||
|
||||
We can now enroll to generate the identity certificates and MSP folder:
|
||||
```
|
||||
fabric-ca-client enroll -u https://recipient:recipientpw@localhost:8054 --caname ca-org2 -M ${PWD}/organizations/peerOrganizations/org2.example.com/users/recipient@org2.example.com/msp --tls.certfiles ${PWD}/organizations/fabric-ca/org2/tls-cert.pem
|
||||
```
|
||||
|
||||
Run the command below to copy the Node OU configuration file into the recipient identity MSP folder.
|
||||
```
|
||||
cp ${PWD}/organizations/peerOrganizations/org2.example.com/msp/config.yaml ${PWD}/organizations/peerOrganizations/org2.example.com/users/recipient@org2.example.com/msp/config.yaml
|
||||
```
|
||||
|
||||
## Mint some tokens
|
||||
|
||||
Now that we have created the identity of the minter, we can invoke the smart contract to mint some tokens.
|
||||
Shift back to the Org1 terminal, we'll set the following environment variables to operate the `peer` CLI as the minter identity from Org1.
|
||||
```
|
||||
export CORE_PEER_TLS_ENABLED=true
|
||||
export CORE_PEER_LOCALMSPID="Org1MSP"
|
||||
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/minter@org1.example.com/msp
|
||||
export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
|
||||
export CORE_PEER_ADDRESS=localhost:7051
|
||||
export TARGET_TLS_OPTIONS="-o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem --peerAddresses localhost:7051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses localhost:9051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt"
|
||||
```
|
||||
|
||||
The last environment variable above will be utilized within the CLI invoke commands to set the target peers for endorsement, and the target ordering service endpoint and TLS options.
|
||||
|
||||
We can then invoke the smart contract to mint 5000 tokens:
|
||||
```
|
||||
peer chaincode invoke $TARGET_TLS_OPTIONS -C mychannel -n token_utxo -c '{"function":"Mint","Args":["5000"]}'
|
||||
```
|
||||
|
||||
The mint function validated that the client is a member of the minter organization, and then created a UTXO with 5000 tokens belonging to the minter client identity.
|
||||
The function returns the UTXO that was created so that we can inspect it. Here is the returned UTXO with JSON formatting applied:
|
||||
```
|
||||
{
|
||||
"utxo_key":"c3706696c537e7bd6940fedfd52f4a3a630d253297db0ecc2b3ba514b45f5e7c.0",
|
||||
"owner":"eDUwOTo6Q049bWludGVyLE9VPWNsaWVudCxPPUh5cGVybGVkZ2VyLFNUPU5vcnRoIENhcm9saW5hLEM9VVM6OkNOPWNhLm9yZzEuZXhhbXBsZS5jb20sTz1vcmcxLmV4YW1wbGUuY29tLEw9RHVyaGFtLFNUPU5vcnRoIENhcm9saW5hLEM9VVM=",
|
||||
"amount":5000
|
||||
}
|
||||
```
|
||||
|
||||
Notice that the utxo_key is set to the transaction id, concatenated with ".0" to indicate that this is the first (and only) UTXO output of the transaction. Your transaction id will be different and unique. The owner is set to the minter's client ID, meaning that only this client can spend the UTXO, and the amount is "5000".
|
||||
|
||||
The utxo_key that was created will be needed when you spend the UTXO in the Transfer function below. Copy the utxo_key value (including the ".0") so that you'll have it available for the Transfer function.
|
||||
|
||||
We can check the minter client's total set of owned UTXOs by calling the `ClientUTXOs` function.
|
||||
```
|
||||
peer chaincode query -C mychannel -n token_utxo -c '{"function":"ClientUTXOs","Args":[]}'
|
||||
```
|
||||
|
||||
The same minted UTXO is returned.
|
||||
|
||||
|
||||
## Transfer tokens
|
||||
|
||||
The minter intends to transfer 100 tokens to the Org2 recipient, but first the Org2 recipient needs to provide their own client ID as the payment address.
|
||||
A client can derive their client ID from their own public certificate, but to be sure the client ID is accurate, the contract has a `ClientID` utility function that simply looks at the callers certificate and returns the calling client's ID.
|
||||
Let's prepare the Org2 terminal by setting the environment variables for the Org2 recipient user.
|
||||
```
|
||||
export PATH=${PWD}/../bin:${PWD}:$PATH
|
||||
export FABRIC_CFG_PATH=$PWD/../config/
|
||||
export CORE_PEER_TLS_ENABLED=true
|
||||
export CORE_PEER_LOCALMSPID="Org2MSP"
|
||||
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org2.example.com/users/recipient@org2.example.com/msp
|
||||
export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
|
||||
export CORE_PEER_ADDRESS=localhost:9051
|
||||
```
|
||||
|
||||
Using the Org2 terminal, the Org2 recipient user can retrieve their own client ID:
|
||||
```
|
||||
peer chaincode query -C mychannel -n token_utxo -c '{"function":"ClientID","Args":[]}'
|
||||
```
|
||||
|
||||
The function returns of recipient's client ID:
|
||||
```
|
||||
eDUwOTo6Q049cmVjaXBpZW50LE9VPWNsaWVudCxPPUh5cGVybGVkZ2VyLFNUPU5vcnRoIENhcm9saW5hLEM9VVM6OkNOPWNhLm9yZzIuZXhhbXBsZS5jb20sTz1vcmcyLmV4YW1wbGUuY29tLEw9SHVyc2xleSxTVD1IYW1wc2hpcmUsQz1VSw==
|
||||
```
|
||||
|
||||
Let's base64 decode the client ID to make sure it is the Org2 recipient user:
|
||||
```
|
||||
echo eDUwOTo6Q049cmVjaXBpZW50LE9VPWNsaWVudCxPPUh5cGVybGVkZ2VyLFNUPU5vcnRoIENhcm9saW5hLEM9VVM6OkNOPWNhLm9yZzIuZXhhbXBsZS5jb20sTz1vcmcyLmV4YW1wbGUuY29tLEw9SHVyc2xleSxTVD1IYW1wc2hpcmUsQz1VSw== | base64 --decode
|
||||
```
|
||||
|
||||
The result shows that the subject and issuer is indeed the recipient user from Org2:
|
||||
```
|
||||
x509::CN=recipient,OU=client,O=Hyperledger,ST=North Carolina,C=US::CN=ca.org2.example.com,O=org2.example.com,L=Hursley,ST=Hampshire,C=UK
|
||||
```
|
||||
|
||||
After the Org2 recipient provides their client ID to the minter, the minter can initiate a transfer of tokens. We'll pass in the utxo_key of the UTXO with 5000 tokens to spend, and request that two new UTXOs get created, a UTXO with 100 tokens for the recipient, and a UTXO with 4900 tokens for the minter as the 'change'. Since the contract will create the UTXO output keys, we'll initially leave the output keys blank.
|
||||
Back in the Org1 terminal, request the UTXO transfer. **Replace YOUR_UTXO_KEY below with the key you saved earlier**:
|
||||
```
|
||||
peer chaincode invoke $TARGET_TLS_OPTIONS -C mychannel -n token_utxo -c '{"function":"Transfer","Args":["[\"YOUR_UTXO_KEY\"]"," [{\"utxo_key\":\"\",\"owner\":\"eDUwOTo6Q049cmVjaXBpZW50LE9VPWNsaWVudCxPPUh5cGVybGVkZ2VyLFNUPU5vcnRoIENhcm9saW5hLEM9VVM6OkNOPWNhLm9yZzIuZXhhbXBsZS5jb20sTz1vcmcyLmV4YW1wbGUuY29tLEw9SHVyc2xleSxTVD1IYW1wc2hpcmUsQz1VSw==\",\"amount\":100},{\"utxo_key\":\"\",\"owner\":\"eDUwOTo6Q049bWludGVyLE9VPWNsaWVudCxPPUh5cGVybGVkZ2VyLFNUPU5vcnRoIENhcm9saW5hLEM9VVM6OkNOPWNhLm9yZzEuZXhhbXBsZS5jb20sTz1vcmcxLmV4YW1wbGUuY29tLEw9RHVyaGFtLFNUPU5vcnRoIENhcm9saW5hLEM9VVM=\",\"amount\":4900}]"]}'
|
||||
```
|
||||
|
||||
The `Transfer` function verifies that the calling client owns the input UTXO, and that the sum of the input amounts equals the sum of the output amounts. It will then delete (spend) the input UTXO, and create the two output UTXOs. If you passed the incorrect UTXO input key, or requested UTXO output values that don't total 5000, you'll get an error indicating as such.
|
||||
|
||||
The new UTXO outputs are returned in the successful response:
|
||||
```
|
||||
[{\"utxo_key\":\"e51c3d19e92326f772e49e8a3e58f2bbf72bc3905e55fcd649b97a91b9b2cf44.0\",\"owner\":\"eDUwOTo6Q049cmVjaXBpZW50LE9VPWNsaWVudCxPPUh5cGVybGVkZ2VyLFNUPU5vcnRoIENhcm9saW5hLEM9VVM6OkNOPWNhLm9yZzIuZXhhbXBsZS5jb20sTz1vcmcyLmV4YW1wbGUuY29tLEw9SHVyc2xleSxTVD1IYW1wc2hpcmUsQz1VSw==\",\"amount\":100},{\"utxo_key\":\"e51c3d19e92326f772e49e8a3e58f2bbf72bc3905e55fcd649b97a91b9b2cf44.1\",\"owner\":\"eDUwOTo6Q049bWludGVyLE9VPWNsaWVudCxPPUh5cGVybGVkZ2VyLFNUPU5vcnRoIENhcm9saW5hLEM9VVM6OkNOPWNhLm9yZzEuZXhhbXBsZS5jb20sTz1vcmcxLmV4YW1wbGUuY29tLEw9RHVyaGFtLFNUPU5vcnRoIENhcm9saW5hLEM9VVM=\",\"amount\":4900}]
|
||||
```
|
||||
|
||||
While still in the Org1 terminal, let's request the minter's UTXOs again:
|
||||
```
|
||||
peer chaincode query -C mychannel -n token_utxo -c '{"function":"ClientUTXOs","Args":[]}'
|
||||
```
|
||||
|
||||
The new UTXO worth 4900 tokens is returned.
|
||||
|
||||
And then using the Org2 terminal, let's request the recipient's UTXOs:
|
||||
```
|
||||
peer chaincode query -C mychannel -n token_utxo -c '{"function":"ClientUTXOs","Args":[]}'
|
||||
```
|
||||
|
||||
The new UTXO worth 100 tokens is returned.
|
||||
|
||||
Congratulations, you've transferred 100 tokens! The Org2 recipient can now transfer tokens to other registered users in the same manner.
|
||||
|
||||
## Clean up
|
||||
|
||||
When you are finished, you can bring down the test network. The command will remove all the nodes of the test network, and delete any ledger data that you created:
|
||||
```
|
||||
./network.sh down
|
||||
```
|
||||
|
||||
## Contract extension ideas
|
||||
|
||||
You can extend the basic UTXO token sample to meet other requirements. For example:
|
||||
|
||||
* Rather than using the default 'majority' endorsement policy, you could set the endorsement policy to a subset of organizations that represent trust anchors for the contract execution.
|
||||
* You could utilize anonymous payment addresses on the UTXO outputs based on private-public key pairs, instead of UTXOs assigned to a client ID. In order to spend the tokens, the client would have to sign the transfer input as proof that they own the address private key, which the contract would then validate, similar to the Bitcoin model in the permissionless blockchain space. However, in a permissioned blockchain such as Fabric, only registered clients are authorized to participate. Furthermore, if you don't want to leak the registered client identity associated with each address, the clients could be registered using an Identity Mixer MSP, so that the client itself is also anonymous in each of the token transactions.
|
||||
217
token-utxo/chaincode-go/chaincode/token_contract.go
Normal file
217
token-utxo/chaincode-go/chaincode/token_contract.go
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
package chaincode
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
|
||||
"github.com/hyperledger/fabric-contract-api-go/contractapi"
|
||||
)
|
||||
|
||||
// SmartContract provides functions for transferring tokens using UTXO transactions
|
||||
type SmartContract struct {
|
||||
contractapi.Contract
|
||||
}
|
||||
|
||||
// UTXO represents an unspent transaction output
|
||||
type UTXO struct {
|
||||
Key string `json:"utxo_key"`
|
||||
Owner string `json:"owner"`
|
||||
Amount int `json:"amount"`
|
||||
}
|
||||
|
||||
// Mint creates a new unspent transaction output (UTXO) owned by the minter
|
||||
func (s *SmartContract) Mint(ctx contractapi.TransactionContextInterface, amount int) (*UTXO, error) {
|
||||
|
||||
// Check minter authorization - this sample assumes Org1 is the central banker with privilege to mint new tokens
|
||||
clientMSPID, err := ctx.GetClientIdentity().GetMSPID()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get MSPID: %v", err)
|
||||
}
|
||||
if clientMSPID != "Org1MSP" {
|
||||
return nil, fmt.Errorf("client is not authorized to mint new tokens")
|
||||
}
|
||||
|
||||
// Get ID of submitting client identity
|
||||
minter, err := ctx.GetClientIdentity().GetID()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get client id: %v", err)
|
||||
}
|
||||
|
||||
if amount <= 0 {
|
||||
return nil, fmt.Errorf("mint amount must be a positive integer")
|
||||
}
|
||||
|
||||
utxo := UTXO{}
|
||||
utxo.Key = ctx.GetStub().GetTxID() + ".0"
|
||||
utxo.Owner = minter
|
||||
utxo.Amount = amount
|
||||
|
||||
// the utxo has a composite key of owner:utxoKey, this enables ClientUTXOs() function to query for an owner's utxos.
|
||||
utxoCompositeKey, err := ctx.GetStub().CreateCompositeKey("utxo", []string{minter, utxo.Key})
|
||||
|
||||
err = ctx.GetStub().PutState(utxoCompositeKey, []byte(strconv.Itoa(amount)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Printf("utxo minted: %+v", utxo)
|
||||
|
||||
return &utxo, nil
|
||||
}
|
||||
|
||||
// Transfer transfers UTXOs containing tokens from client to recipient(s)
|
||||
func (s *SmartContract) Transfer(ctx contractapi.TransactionContextInterface, utxoInputKeys []string, utxoOutputs []UTXO) ([]UTXO, error) {
|
||||
|
||||
// Get ID of submitting client identity
|
||||
clientID, err := ctx.GetClientIdentity().GetID()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get client id: %v", err)
|
||||
}
|
||||
|
||||
// Validate and summarize utxo inputs
|
||||
var utxoInputs []*UTXO
|
||||
var totalInputAmount int
|
||||
for _, utxoInputKey := range utxoInputKeys {
|
||||
utxoInputCompositeKey, err := ctx.GetStub().CreateCompositeKey("utxo", []string{clientID, utxoInputKey})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create composite key: %v", err)
|
||||
}
|
||||
|
||||
// validate that client has a utxo matching the input key
|
||||
valueBytes, err := ctx.GetStub().GetState(utxoInputCompositeKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read utxoInputCompositeKey %s from world state: %v", utxoInputCompositeKey, err)
|
||||
}
|
||||
|
||||
if valueBytes == nil {
|
||||
return nil, fmt.Errorf("utxoInput %s not found for client %s", utxoInputKey, clientID)
|
||||
}
|
||||
|
||||
amount, _ := strconv.Atoi(string(valueBytes)) // Error handling not needed since Itoa() was used when setting the utxo amount, guaranteeing it was an integer.
|
||||
|
||||
utxoInput := &UTXO{
|
||||
Key: utxoInputKey,
|
||||
Owner: clientID,
|
||||
Amount: amount,
|
||||
}
|
||||
|
||||
totalInputAmount += amount
|
||||
utxoInputs = append(utxoInputs, utxoInput)
|
||||
}
|
||||
|
||||
// Validate and summarize utxo outputs
|
||||
var totalOutputAmount int
|
||||
txID := ctx.GetStub().GetTxID()
|
||||
for i, utxoOutput := range utxoOutputs {
|
||||
|
||||
if utxoOutput.Amount <= 0 {
|
||||
return nil, fmt.Errorf("utxo output amount must be a positive integer")
|
||||
}
|
||||
|
||||
utxoOutputs[i].Key = fmt.Sprintf("%s.%d", txID, i)
|
||||
|
||||
totalOutputAmount += utxoOutput.Amount
|
||||
}
|
||||
|
||||
// Validate total inputs equals total outputs
|
||||
if totalInputAmount != totalOutputAmount {
|
||||
return nil, fmt.Errorf("total utxoInput amount %d does not equal total utxoOutput amount %d", totalInputAmount, totalOutputAmount)
|
||||
}
|
||||
|
||||
// Since the transaction is valid, now delete utxo inputs from owner's state
|
||||
for _, utxoInput := range utxoInputs {
|
||||
|
||||
utxoInputCompositeKey, err := ctx.GetStub().CreateCompositeKey("utxo", []string{utxoInput.Owner, utxoInput.Key})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create composite key: %v", err)
|
||||
}
|
||||
|
||||
err = ctx.GetStub().DelState(utxoInputCompositeKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Printf("utxoInput deleted: %+v", utxoInput)
|
||||
}
|
||||
|
||||
// Create utxo outputs using a composite key based on the owner and utxo key
|
||||
for _, utxoOutput := range utxoOutputs {
|
||||
utxoOutputCompositeKey, err := ctx.GetStub().CreateCompositeKey("utxo", []string{utxoOutput.Owner, utxoOutput.Key})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create composite key: %v", err)
|
||||
}
|
||||
|
||||
err = ctx.GetStub().PutState(utxoOutputCompositeKey, []byte(strconv.Itoa(utxoOutput.Amount)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Printf("utxoOutput created: %+v", utxoOutput)
|
||||
}
|
||||
|
||||
return utxoOutputs, nil
|
||||
}
|
||||
|
||||
// ClientUTXOs returns all UTXOs owned by the calling client
|
||||
func (s *SmartContract) ClientUTXOs(ctx contractapi.TransactionContextInterface) ([]*UTXO, error) {
|
||||
|
||||
// Get ID of submitting client identity
|
||||
clientID, err := ctx.GetClientIdentity().GetID()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get client id: %v", err)
|
||||
}
|
||||
|
||||
// since utxos have a composite key of owner:utxoKey, we can query for all utxos matching owner:*
|
||||
utxoResultsIterator, err := ctx.GetStub().GetStateByPartialCompositeKey("utxo", []string{clientID})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer utxoResultsIterator.Close()
|
||||
|
||||
var utxos []*UTXO
|
||||
for utxoResultsIterator.HasNext() {
|
||||
utxoRecord, err := utxoResultsIterator.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// composite key is expected to be owner:utxoKey
|
||||
_, compositeKeyParts, err := ctx.GetStub().SplitCompositeKey(utxoRecord.Key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(compositeKeyParts) != 2 {
|
||||
return nil, fmt.Errorf("expected composite key with two parts (owner:utxoKey)")
|
||||
}
|
||||
|
||||
utxoKey := compositeKeyParts[1] // owner is at [0], utxoKey is at[1]
|
||||
|
||||
if utxoRecord.Value == nil {
|
||||
return nil, fmt.Errorf("utxo %s has no value", utxoKey)
|
||||
}
|
||||
|
||||
amount, _ := strconv.Atoi(string(utxoRecord.Value)) // Error handling not needed since Itoa() was used when setting the utxo amount, guaranteeing it was an integer.
|
||||
|
||||
utxo := &UTXO{
|
||||
Key: utxoKey,
|
||||
Owner: clientID,
|
||||
Amount: amount,
|
||||
}
|
||||
|
||||
utxos = append(utxos, utxo)
|
||||
}
|
||||
return utxos, nil
|
||||
}
|
||||
|
||||
// ClientID returns the client id of the calling client
|
||||
// Users can use this function to get their own client id, which they can then give to others as the payment address
|
||||
func (s *SmartContract) ClientID(ctx contractapi.TransactionContextInterface) (string, error) {
|
||||
|
||||
// Get ID of submitting client identity
|
||||
clientID, err := ctx.GetClientIdentity().GetID()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get client id: %v", err)
|
||||
}
|
||||
|
||||
return clientID, nil
|
||||
}
|
||||
5
token-utxo/chaincode-go/go.mod
Normal file
5
token-utxo/chaincode-go/go.mod
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
module github.com/hyperledger/fabric-samples/token-utxo/chaincode-go
|
||||
|
||||
go 1.14
|
||||
|
||||
require github.com/hyperledger/fabric-contract-api-go v1.1.0
|
||||
145
token-utxo/chaincode-go/go.sum
Normal file
145
token-utxo/chaincode-go/go.sum
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/DATA-DOG/go-txdb v0.1.3/go.mod h1:DhAhxMXZpUJVGnT+p9IbzJoRKvlArO2pkHjnGX7o0n0=
|
||||
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
|
||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
github.com/cucumber/godog v0.8.0/go.mod h1:Cp3tEV1LRAyH/RuCThcxHS/+9ORZ+FMzPva2AZ5Ki+A=
|
||||
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/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
|
||||
github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w=
|
||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonreference v0.19.2 h1:o20suLFB4Ri0tuzpWtyHlh7E7HnkqTNLq6aR6WVNS1w=
|
||||
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
|
||||
github.com/go-openapi/spec v0.19.4 h1:ixzUSnHTd6hCemgtAJgluaTSGYpLNpJY4mA2DIkdOAo=
|
||||
github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
|
||||
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU=
|
||||
github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
|
||||
github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs=
|
||||
github.com/gobuffalo/packd v0.3.0 h1:eMwymTkA1uXsqxS0Tpoop3Lc0u3kTfiMBE6nKtQU4g4=
|
||||
github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q=
|
||||
github.com/gobuffalo/packr v1.30.1 h1:hu1fuVR3fXEZR7rXNW3h8rqSML8EVAf6KNm0NKO/wKg=
|
||||
github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIaviy289eRuk=
|
||||
github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hyperledger/fabric-chaincode-go v0.0.0-20200424173110-d7076418f212 h1:1i4lnpV8BDgKOLi1hgElfBqdHXjXieSuj8629mwBZ8o=
|
||||
github.com/hyperledger/fabric-chaincode-go v0.0.0-20200424173110-d7076418f212/go.mod h1:N7H3sA7Tx4k/YzFq7U0EPdqJtqvM4Kild0JoCc7C0Dc=
|
||||
github.com/hyperledger/fabric-contract-api-go v1.1.0 h1:K9uucl/6eX3NF0/b+CGIiO1IPm1VYQxBkpnVGJur2S4=
|
||||
github.com/hyperledger/fabric-contract-api-go v1.1.0/go.mod h1:nHWt0B45fK53owcFpLtAe8DH0Q5P068mnzkNXMPSL7E=
|
||||
github.com/hyperledger/fabric-protos-go v0.0.0-20190919234611-2a87503ac7c9/go.mod h1:xVYTjK4DtZRBxZ2D9aE4y6AbLaPwue2o/criQyQbVD0=
|
||||
github.com/hyperledger/fabric-protos-go v0.0.0-20200424173316-dd554ba3746e h1:9PS5iezHk/j7XriSlNuSQILyCOfcZ9wZ3/PiucmSE8E=
|
||||
github.com/hyperledger/fabric-protos-go v0.0.0-20200424173316-dd554ba3746e/go.mod h1:xVYTjK4DtZRBxZ2D9aE4y6AbLaPwue2o/criQyQbVD0=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||
github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
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.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/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=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190710143415-6ec70d6a5542 h1:6ZQFf1D2YYDDI7eSwW8adlkkavTB9sw5I24FVtEvNUQ=
|
||||
golang.org/x/sys v0.0.0-20190710143415-6ec70d6a5542/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b h1:lohp5blsw53GBXtLyLNaTXPXS9pJ1tiTw61ZHUoE9Qw=
|
||||
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/grpc v1.23.0 h1:AzbTB6ux+okLTzP8Ru1Xs41C303zdcfEht7MQnYJt5A=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
23
token-utxo/chaincode-go/token_utxo.go
Normal file
23
token-utxo/chaincode-go/token_utxo.go
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/hyperledger/fabric-contract-api-go/contractapi"
|
||||
"github.com/hyperledger/fabric-samples/token-utxo/chaincode-go/chaincode"
|
||||
)
|
||||
|
||||
func main() {
|
||||
tokenChaincode, err := contractapi.NewChaincode(&chaincode.SmartContract{})
|
||||
if err != nil {
|
||||
log.Panicf("Error creating token-utxo chaincode: %v", err)
|
||||
}
|
||||
|
||||
if err := tokenChaincode.Start(); err != nil {
|
||||
log.Panicf("Error starting token-utxo chaincode: %v", err)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue